Merge "Modify the logic that updates the information of enabled services in ManagedServices to handle the visible background user in MUMD" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index b4127c5..df4a3e5 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",
@@ -1399,6 +1429,18 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.app.appfunctions.exported-flags-aconfig-java",
+    aconfig_declarations: "android.app.appfunctions.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    mode: "exported",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.permission",
+    ],
+    min_sdk_version: "30",
+}
+
 // Adaptive Auth
 aconfig_declarations {
     name: "android.adaptiveauth.flags-aconfig",
@@ -1549,3 +1591,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/Ravenwood.bp b/Ravenwood.bp
index 5f32ba0..ec58210 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -50,7 +50,7 @@
 framework_minus_apex_cmd = "$(location hoststubgen) " +
     "@$(location :ravenwood-standard-options) " +
     "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
-    "--out-impl-jar $(location ravenwood.jar) " +
+    "--out-jar $(location ravenwood.jar) " +
     "--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
     "--policy-override-file $(location :ravenwood-framework-policies) " +
     "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) "
@@ -183,7 +183,7 @@
         "--stats-file $(location hoststubgen_services.core_stats.csv) " +
         "--supported-api-list-file $(location hoststubgen_services.core_apis.csv) " +
 
-        "--out-impl-jar $(location ravenwood.jar) " +
+        "--out-jar $(location ravenwood.jar) " +
 
         "--gen-keep-all-file $(location hoststubgen_services.core_keep_all.txt) " +
         "--gen-input-dump-file $(location hoststubgen_services.core_dump.txt) " +
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 5db0772..dfacbc4 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -140,11 +140,17 @@
   "ravenwood-presubmit": [
     {
       "name": "CtsUtilTestCasesRavenwood",
-      "host": true
+      "host": true,
+      "file_patterns": [
+        "[Rr]avenwood"
+      ]
     },
     {
       "name": "RavenwoodBivalentTest",
-      "host": true
+      "host": true,
+      "file_patterns": [
+        "[Rr]avenwood"
+      ]
     }
   ],
   "postsubmit-managedprofile-stress": [
diff --git a/apct-tests/perftests/tracing/Android.bp b/apct-tests/perftests/tracing/Android.bp
new file mode 100644
index 0000000..08e365b
--- /dev/null
+++ b/apct-tests/perftests/tracing/Android.bp
@@ -0,0 +1,33 @@
+// 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.
+
+android_test {
+    name: "ProtologPerfTests",
+    team: "trendy_team_windowing_tools",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.annotation_annotation",
+        "apct-perftests-utils",
+        "collector-device-lib",
+        "platform-test-annotations",
+    ],
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+    data: [":perfetto_artifacts"],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/apct-tests/perftests/tracing/AndroidManifest.xml b/apct-tests/perftests/tracing/AndroidManifest.xml
new file mode 100644
index 0000000..68125df
--- /dev/null
+++ b/apct-tests/perftests/tracing/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.perftests.protolog">
+
+    <!-- For perfetto trace files -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.perftests.protolog">
+        <!-- <meta-data android:name="listener" android:value="android.protolog.ProtologPerfRunListener" /> -->
+    </instrumentation>
+</manifest>
diff --git a/apct-tests/perftests/tracing/AndroidTest.xml b/apct-tests/perftests/tracing/AndroidTest.xml
new file mode 100644
index 0000000..871a20c
--- /dev/null
+++ b/apct-tests/perftests/tracing/AndroidTest.xml
@@ -0,0 +1,67 @@
+<?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.
+-->
+<configuration description="Runs ProtologPerfTests metric instrumentation.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-metric-instrumentation" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="ProtologPerfTests.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" />
+        <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+        <option name="run-command" value="cmd window dismiss-keyguard" />
+        <option name="run-command" value="cmd package compile -m speed com.android.perftests.wm" />
+    </target_preparer>
+
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.perftests.protolog" />
+        <option name="hidden-api-checks" value="false"/>
+
+        <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+    </test>
+
+    <!-- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/local/tmp/ProtologPerfTests" /> -->
+        <!-- Needed for pulling the collected trace config on to the host -->
+        <!-- <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector> -->
+</configuration>
diff --git a/apct-tests/perftests/tracing/OWNERS b/apct-tests/perftests/tracing/OWNERS
new file mode 100644
index 0000000..3f3308c
--- /dev/null
+++ b/apct-tests/perftests/tracing/OWNERS
@@ -0,0 +1 @@
+include platform/development:/tools/winscope/OWNERS
diff --git a/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java b/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
new file mode 100644
index 0000000..f4759b8
--- /dev/null
+++ b/apct-tests/perftests/tracing/src/com/android/internal/protolog/ProtoLogPerfTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.protolog;
+
+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.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public class ProtoLogPerfTest {
+    private final boolean mLogToProto;
+    private final boolean mLogToLogcat;
+
+    /**
+     * 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
+    public static void init() {
+        ProtoLog.init(TestProtoLogGroup.values());
+    }
+
+    @Before
+    public void setUp() {
+        TestProtoLogGroup.TEST_GROUP.setLogToProto(mLogToProto);
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(mLogToLogcat);
+    }
+
+    @Test
+    public void log_Processed_NoArgs() {
+        final var protoLog = ProtoLog.getSingleInstance();
+        final var perfMonitor = new PerfMonitor();
+
+        while (perfMonitor.keepRunning()) {
+            protoLog.log(
+                    LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
+                    0, (Object[]) null);
+        }
+    }
+
+    @Test
+    public void log_Processed_WithArgs() {
+        final var protoLog = ProtoLog.getSingleInstance();
+        final var perfMonitor = new PerfMonitor();
+
+        while (perfMonitor.keepRunning()) {
+            protoLog.log(
+                    LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 123,
+                    0b1110101001010100,
+                    new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
+        }
+    }
+
+    @Test
+    public void log_Unprocessed_NoArgs() {
+        final var perfMonitor = new PerfMonitor();
+
+        while (perfMonitor.keepRunning()) {
+            ProtoLog.d(TestProtoLogGroup.TEST_GROUP, "Test message");
+        }
+    }
+
+    @Test
+    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);
+        }
+    }
+
+    private enum TestProtoLogGroup implements IProtoLogGroup {
+        TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+        private final boolean mEnabled;
+        private volatile boolean mLogToProto;
+        private volatile boolean mLogToLogcat;
+        private final String mTag;
+
+        /**
+         * @param enabled set to false to exclude all log statements for this group from
+         *     compilation, they will not be available in runtime.
+         * @param logToProto enable binary logging for the group
+         * @param logToLogcat enable text logging for the group
+         * @param tag name of the source of the logged message
+         */
+        TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+            this.mEnabled = enabled;
+            this.mLogToProto = logToProto;
+            this.mLogToLogcat = logToLogcat;
+            this.mTag = tag;
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return mEnabled;
+        }
+
+        @Override
+        public boolean isLogToProto() {
+            return mLogToProto;
+        }
+
+        @Override
+        public boolean isLogToLogcat() {
+            return mLogToLogcat;
+        }
+
+        @Override
+        public boolean isLogToAny() {
+            return mLogToLogcat || mLogToProto;
+        }
+
+        @Override
+        public String getTag() {
+            return mTag;
+        }
+
+        @Override
+        public void setLogToProto(boolean logToProto) {
+            this.mLogToProto = logToProto;
+        }
+
+        @Override
+        public void setLogToLogcat(boolean logToLogcat) {
+            this.mLogToLogcat = logToLogcat;
+        }
+
+        @Override
+        public int getId() {
+            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/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 3d25ed5..97c6e25 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -5808,9 +5808,10 @@
 
     static void dumpHelp(PrintWriter pw) {
         pw.println("Job Scheduler (jobscheduler) dump options:");
-        pw.println("  [-h] [package] ...");
+        pw.println("  [-h] [package] [--proto] ...");
         pw.println("    -h: print this help");
         pw.println("  [package] is an optional package name to limit the output to.");
+        pw.println("    --proto: output dump in protocol buffer format.");
     }
 
     /** Sort jobs by caller UID, then by Job ID. */
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/Android.bp b/api/Android.bp
index 341be3d53..533f9f6 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -87,6 +87,7 @@
         "framework-permission",
         "framework-permission-s",
         "framework-profiling",
+        "framework-photopicker",
         "framework-scheduling",
         "framework-sdkextensions",
         "framework-statsd",
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 f96e4fd..5685221 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
@@ -7964,13 +7964,13 @@
     field public static final String LOCK_TASK_POLICY = "lockTask";
     field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended";
     field public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked";
-    field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity";
+    field public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity";
     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 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);
@@ -8756,6 +8756,8 @@
     method public int getResultCode();
     method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
     method public boolean isSuccess();
+    method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
+    method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
     field public static final String PROPERTY_RETURN_VALUE = "returnValue";
@@ -8767,13 +8769,6 @@
     field public static final int RESULT_TIMED_OUT = 5; // 0x5
   }
 
-  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final class ExecuteAppFunctionResponse.Builder {
-    ctor public ExecuteAppFunctionResponse.Builder(@NonNull android.app.appsearch.GenericDocument);
-    ctor public ExecuteAppFunctionResponse.Builder(int, @NonNull String);
-    method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse build();
-    method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse.Builder setExtras(@NonNull android.os.Bundle);
-  }
-
 }
 
 package android.app.assist {
@@ -9875,6 +9870,7 @@
     field public static final int RESULT_DISCOVERY_TIMEOUT = 2; // 0x2
     field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
     field public static final int RESULT_OK = -1; // 0xffffffff
+    field @FlaggedApi("android.companion.association_failure_code") public static final int RESULT_SECURITY_ERROR = 4; // 0x4
     field public static final int RESULT_USER_REJECTED = 1; // 0x1
   }
 
@@ -9884,7 +9880,7 @@
     method public void onAssociationPending(@NonNull android.content.IntentSender);
     method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender);
     method public abstract void onFailure(@Nullable CharSequence);
-    method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int);
+    method @FlaggedApi("android.companion.association_failure_code") public void onFailure(int, @Nullable CharSequence);
   }
 
   public abstract class CompanionDeviceService extends android.app.Service {
@@ -34115,7 +34111,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";
@@ -34165,7 +34161,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,7 +43964,10 @@
     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_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_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_esos_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_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_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_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_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";
@@ -46100,7 +46099,7 @@
     method public int getCardIdForDefaultEuicc();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig();
     method public int getCarrierIdFromSimMccMnc();
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void getCarrierRestrictionStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public void getCarrierRestrictionStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public int getDataNetworkType();
@@ -54921,6 +54920,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);
@@ -54949,6 +54950,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();
@@ -55009,6 +55011,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 9c93c3a..ba16037 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -61,6 +61,7 @@
     field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE";
     field public static final String BIND_DOMAIN_VERIFICATION_AGENT = "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
     field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
+    field @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public static final String BIND_EXPLICIT_HEALTH_CHECK_SERVICE = "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
     field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
     field public static final String BIND_FIELD_CLASSIFICATION_SERVICE = "android.permission.BIND_FIELD_CLASSIFICATION_SERVICE";
     field public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE";
@@ -201,7 +202,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 +1297,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 +1309,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 +1329,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 +1345,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 +1423,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
@@ -3443,7 +3444,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.ActivityPolicyExemption> CREATOR;
   }
 
-  public static final class ActivityPolicyExemption.Builder {
+  @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final class ActivityPolicyExemption.Builder {
     ctor public ActivityPolicyExemption.Builder();
     method @NonNull public android.companion.virtual.ActivityPolicyExemption build();
     method @NonNull public android.companion.virtual.ActivityPolicyExemption.Builder setComponentName(@NonNull android.content.ComponentName);
@@ -3469,6 +3470,7 @@
   public static interface VirtualDeviceManager.ActivityListener {
     method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle, @Nullable android.content.IntentSender);
     method public void onDisplayEmpty(int);
+    method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onSecureWindowShown(int, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
     method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
     method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int);
   }
@@ -3694,9 +3696,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 +3714,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 {
@@ -4627,6 +4633,7 @@
     method public int getCommittedSessionId();
     method @NonNull public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages();
     method public int getRollbackId();
+    method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") public int getRollbackImpactLevel();
     method public boolean isStaged();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
@@ -6742,6 +6749,14 @@
     field public static final int STATUS_OK = 0; // 0x0
   }
 
+  @FlaggedApi("android.media.soundtrigger.generic_model_api") public static final class SoundTrigger.GenericSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
+    ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int);
+    ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.GenericSoundModel> CREATOR;
+  }
+
   public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
     ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
     method public int describeContents();
@@ -8083,6 +8098,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 +11171,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 +14236,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 ce0d38f..42f7615 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -597,19 +597,19 @@
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
-    method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public void forceSetMaxPolicyStorageLimit(int);
+    method @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public void forceSetMaxPolicyStorageLimit(int);
     method public void forceUpdateUserSetupComplete(int);
     method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
     method @Deprecated public int getDeviceOwnerType(@NonNull android.content.ComponentName);
     method @Nullable public String getDevicePolicyManagementRoleHolderUpdaterPackage();
     method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String);
-    method @FlaggedApi("android.app.admin.flags.headless_device_owner_provisioning_fix_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getHeadlessDeviceOwnerMode();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getHeadlessDeviceOwnerMode();
     method public long getLastBugReportRequestTime();
     method public long getLastNetworkLogRetrievalTime();
     method public long getLastSecurityLogRetrievalTime();
     method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps();
-    method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public int getPolicySizeForAdmin(@NonNull android.app.admin.EnforcingAdmin);
+    method @RequiresPermission("android.permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT") public int getPolicySizeForAdmin(@NonNull android.app.admin.EnforcingAdmin);
     method public boolean isCurrentInputMethodSetByOwner();
     method public boolean isFactoryResetProtectionPolicySupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isNewUserDisclaimerAcknowledged();
@@ -680,7 +680,7 @@
   }
 
   public final class EnforcingAdmin implements android.os.Parcelable {
-    ctor @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_internal_bug_fix_enabled") public EnforcingAdmin(@NonNull String, @NonNull android.app.admin.Authority, @NonNull android.os.UserHandle, @Nullable android.content.ComponentName);
+    ctor public EnforcingAdmin(@NonNull String, @NonNull android.app.admin.Authority, @NonNull android.os.UserHandle, @Nullable android.content.ComponentName);
   }
 
   public final class FlagUnion extends android.app.admin.ResolutionMechanism<java.lang.Integer> {
@@ -1269,10 +1269,6 @@
 
 package android.content.rollback {
 
-  public final class RollbackInfo implements android.os.Parcelable {
-    method @FlaggedApi("android.content.pm.recoverability_detection") public int getRollbackImpactLevel();
-  }
-
   public final class RollbackManager {
     method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void blockRollbackManager(long);
     method @RequiresPermission(android.Manifest.permission.TEST_MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
@@ -1542,6 +1538,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);
@@ -1797,11 +1797,15 @@
 
   public class InputSettings {
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") public static int getAccessibilityBounceKeysThreshold(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") public static int getAccessibilityRepeatKeysDelay(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") public static int getAccessibilityRepeatKeysTimeout(@NonNull android.content.Context);
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") public static int getAccessibilitySlowKeysThreshold(@NonNull android.content.Context);
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") public static boolean isAccessibilityMouseKeysEnabled(@NonNull android.content.Context);
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") public static boolean isAccessibilityStickyKeysEnabled(@NonNull android.content.Context);
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_bounce_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityBounceKeysThreshold(@NonNull android.content.Context, int);
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_mouse_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityMouseKeysEnabled(@NonNull android.content.Context, boolean);
+    method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityRepeatKeysDelay(@NonNull android.content.Context, int);
+    method @FlaggedApi("com.android.hardware.input.keyboard_repeat_keys") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityRepeatKeysTimeout(@NonNull android.content.Context, int);
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_slow_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilitySlowKeysThreshold(@NonNull android.content.Context, int);
     method @FlaggedApi("com.android.hardware.input.keyboard_a11y_sticky_keys_flag") @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public static void setAccessibilityStickyKeysEnabled(@NonNull android.content.Context, boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float);
@@ -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
@@ -3676,6 +3686,7 @@
 
   public static final class Display.Mode implements android.os.Parcelable {
     ctor public Display.Mode(int, int, float);
+    method public boolean isSynthetic();
     method public boolean matches(int, int, float);
   }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e57630b..b83be6b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -66,6 +66,7 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IpcDataCache;
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -233,6 +234,48 @@
     private static final RateLimitingCache<List<RunningAppProcessInfo>> mRunningProcessesCache =
             new RateLimitingCache<>(10, 4);
 
+    /** Rate-Limiting Cache that allows no more than 200 calls to the service per second. */
+    private static final RateLimitingCache<List<ProcessErrorStateInfo>> mErrorProcessesCache =
+            new RateLimitingCache<>(10, 2);
+
+    /**
+     * Query handler for mGetCurrentUserIdCache - returns a cached value of the current foreground
+     * user id if the backstage_power/android.app.cache_get_current_user_id flag is enabled.
+     */
+    private static final IpcDataCache.QueryHandler<Void, Integer> mGetCurrentUserIdQuery =
+            new IpcDataCache.QueryHandler<>() {
+                @Override
+                public Integer apply(Void query) {
+                    try {
+                        return getService().getCurrentUserId();
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+
+                @Override
+                public boolean shouldBypassCache(Void query) {
+                    // If the flag to enable the new caching behavior is off, bypass the cache.
+                    return !Flags.cacheGetCurrentUserId();
+                }
+            };
+
+    /** A cache which maintains the current foreground user id. */
+    private static final IpcDataCache<Void, Integer> mGetCurrentUserIdCache =
+            new IpcDataCache<>(1, IpcDataCache.MODULE_SYSTEM,
+                    /* api= */ "getCurrentUserId", /* cacheName= */ "CurrentUserIdCache",
+                    mGetCurrentUserIdQuery);
+
+    /**
+     * The current foreground user has changed - invalidate the cache. Currently only called from
+     * UserController when a user switch occurs.
+     * @hide
+     */
+    public static void invalidateGetCurrentUserIdCache() {
+        IpcDataCache.invalidateCache(
+                IpcDataCache.MODULE_SYSTEM, /* api= */ "getCurrentUserId");
+    }
+
     /**
      * Map of callbacks that have registered for {@link UidFrozenStateChanged} events.
      * Will be called when a Uid has become frozen or unfrozen.
@@ -3685,6 +3728,16 @@
      * specified.
      */
     public List<ProcessErrorStateInfo> getProcessesInErrorState() {
+        if (Flags.rateLimitGetProcessesInErrorState()) {
+            return mErrorProcessesCache.get(() -> {
+                return getProcessesInErrorStateInternal();
+            });
+        } else {
+            return getProcessesInErrorStateInternal();
+        }
+    }
+
+    private List<ProcessErrorStateInfo> getProcessesInErrorStateInternal() {
         try {
             return getService().getProcessesInErrorState();
         } catch (RemoteException e) {
@@ -5230,11 +5283,7 @@
     })
     @android.ravenwood.annotation.RavenwoodReplace
     public static int getCurrentUser() {
-        try {
-            return getService().getCurrentUserId();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return mGetCurrentUserIdCache.query(null);
     }
 
     /** @hide */
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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 21396a1..8fd3326 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7459,15 +7459,15 @@
         }
 
         /**
-         * Similar to {@link #onOpChanged(String, String, int)} but includes the device for which
-         * the op mode has changed.
+         * Similar to {@link #onOpChanged(String, String)} but includes user and the device for
+         * which the op mode has changed.
          *
          * <p> Implement this method if callbacks are required on all devices.
          * If not implemented explicitly, the default implementation will notify for op changes
-         * on the default device {@link VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT} only.
+         * on the default device only.
          *
-         * <p> If implemented, {@link #onOpChanged(String, String, int)}
-         * will not be called automatically.
+         * <p> If implemented, {@link #onOpChanged(String, String)} will not be called
+         * automatically.
          *
          * @param op The Op that changed.
          * @param packageName Package of the app whose Op changed.
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 0ff5514..1e9a79b 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -210,6 +210,11 @@
     public static final int START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE = 7;
 
     /**
+     * @see #getMonoticCreationTimeMs
+     */
+    private long mMonoticCreationTimeMs;
+
+    /**
      * @see #getStartupState
      */
     private @StartupState int mStartupState;
@@ -487,6 +492,15 @@
     }
 
     /**
+     * Monotonic elapsed time persisted across reboots.
+     *
+     * @hide
+     */
+    public long getMonoticCreationTimeMs() {
+        return mMonoticCreationTimeMs;
+    }
+
+    /**
      * The process id.
      *
      * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
@@ -669,7 +683,9 @@
     }
 
     /** @hide */
-    public ApplicationStartInfo() {}
+    public ApplicationStartInfo(long monotonicCreationTimeMs) {
+        mMonoticCreationTimeMs = monotonicCreationTimeMs;
+    }
 
     /** @hide */
     public ApplicationStartInfo(ApplicationStartInfo other) {
@@ -686,6 +702,7 @@
         mStartIntent = other.mStartIntent;
         mLaunchMode = other.mLaunchMode;
         mWasForceStopped = other.mWasForceStopped;
+        mMonoticCreationTimeMs = other.mMonoticCreationTimeMs;
     }
 
     private ApplicationStartInfo(@NonNull Parcel in) {
@@ -708,6 +725,7 @@
                 in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
         mLaunchMode = in.readInt();
         mWasForceStopped = in.readBoolean();
+        mMonoticCreationTimeMs = in.readLong();
     }
 
     private static String intern(@Nullable String source) {
@@ -786,6 +804,7 @@
         }
         proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
         proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped);
+        proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonoticCreationTimeMs);
         proto.end(token);
     }
 
@@ -869,6 +888,10 @@
                     mWasForceStopped = proto.readBoolean(
                             ApplicationStartInfoProto.WAS_FORCE_STOPPED);
                     break;
+                case (int) ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS:
+                    mMonoticCreationTimeMs = proto.readLong(
+                            ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS);
+                    break;
             }
         }
         proto.end(token);
@@ -881,6 +904,8 @@
         sb.append(prefix)
                 .append("ApplicationStartInfo ").append(seqSuffix).append(':')
                 .append('\n')
+                .append(" monotonicCreationTimeMs=").append(mMonoticCreationTimeMs)
+                .append('\n')
                 .append(" pid=").append(mPid)
                 .append(" realUid=").append(mRealUid)
                 .append(" packageUid=").append(mPackageUid)
@@ -949,14 +974,15 @@
             && mDefiningUid == o.mDefiningUid && mReason == o.mReason
             && mStartupState == o.mStartupState && mStartType == o.mStartType
             && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName)
-            && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped;
+            && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped
+            && mMonoticCreationTimeMs == o.mMonoticCreationTimeMs;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
-                mStartType, mLaunchMode, mProcessName,
-                mStartupTimestampsNs);
+                mStartType, mLaunchMode, mProcessName, mStartupTimestampsNs,
+                mMonoticCreationTimeMs);
     }
 
     private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 62b5412..e0a9371 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -49,7 +49,7 @@
 
     /**
      * Rule is of an unknown type. This is the default value if not provided by the owning app,
-     * and the value returned if the true type was added in an API level lower than the calling
+     * and the value returned if the true type was added in an API level higher than the calling
      * app's targetSdk.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
@@ -315,7 +315,8 @@
     }
 
     /**
-     * Gets the zen policy.
+     * Gets the {@link ZenPolicy} applied if {@link #getInterruptionFilter()} is
+     * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY}.
      */
     @Nullable
     public ZenPolicy getZenPolicy() {
@@ -345,6 +346,17 @@
 
     /**
      * Sets the interruption filter that is applied when this rule is active.
+     *
+     * <ul>
+     *     <li>When {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY}, the rule will use
+     *     the {@link ZenPolicy} supplied to {@link #setZenPolicy} (or a default one).
+     *     <li>When {@link NotificationManager#INTERRUPTION_FILTER_ALARMS} or
+     *     {@link NotificationManager#INTERRUPTION_FILTER_NONE}, the rule will use a fixed
+     *     {@link ZenPolicy} matching the filter.
+     *     <li>When {@link NotificationManager#INTERRUPTION_FILTER_ALL}, the rule will not block
+     *     notifications, but can still have {@link ZenDeviceEffects}.
+     * </ul>
+     *
      * @param interruptionFilter The do not disturb mode to enter when this rule is active.
      */
     public void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
@@ -374,7 +386,8 @@
     }
 
     /**
-     * Sets the zen policy.
+     * Sets the {@link ZenPolicy} applied if {@link #getInterruptionFilter()} is
+     * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY}.
      *
      * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
      * a {@code null} value here means the previous policy is retained.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e99ba84..7a36fbb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2706,14 +2706,9 @@
         if (mAllowlistToken == null) {
             mAllowlistToken = processAllowlistToken;
         }
-        if (Flags.secureAllowlistToken()) {
-            // Propagate this token to all pending intents that are unmarshalled from the parcel,
-            // or keep the one we're already propagating, if that's the case.
-            if (!parcel.hasClassCookie(PendingIntent.class)) {
-                parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
-            }
-        } else {
-            // Propagate this token to all pending intents that are unmarshalled from the parcel.
+        // Propagate this token to all pending intents that are unmarshalled from the parcel,
+        // or keep the one we're already propagating, if that's the case.
+        if (!parcel.hasClassCookie(PendingIntent.class)) {
             parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
         }
 
@@ -3333,28 +3328,22 @@
             PendingIntent.addOnMarshaledListener(addedListener);
         }
         try {
-            if (Flags.secureAllowlistToken()) {
-                boolean mustClearCookie = false;
-                if (!parcel.hasClassCookie(Notification.class)) {
-                    // This is the "root" notification, and not an "inner" notification (including
-                    // publicVersion or anything else that might be embedded in extras). So we want
-                    // to use its token for every inner notification (might be null).
-                    parcel.setClassCookie(Notification.class, mAllowlistToken);
-                    mustClearCookie = true;
-                }
-                try {
-                    // IMPORTANT: Add marshaling code in writeToParcelImpl as we
-                    // want to intercept all pending events written to the parcel.
-                    writeToParcelImpl(parcel, flags);
-                } finally {
-                    if (mustClearCookie) {
-                        parcel.removeClassCookie(Notification.class, mAllowlistToken);
-                    }
-                }
-            } else {
+            boolean mustClearCookie = false;
+            if (!parcel.hasClassCookie(Notification.class)) {
+                // This is the "root" notification, and not an "inner" notification (including
+                // publicVersion or anything else that might be embedded in extras). So we want
+                // to use its token for every inner notification (might be null).
+                parcel.setClassCookie(Notification.class, mAllowlistToken);
+                mustClearCookie = true;
+            }
+            try {
                 // IMPORTANT: Add marshaling code in writeToParcelImpl as we
                 // want to intercept all pending events written to the parcel.
                 writeToParcelImpl(parcel, flags);
+            } finally {
+                if (mustClearCookie) {
+                    parcel.removeClassCookie(Notification.class, mAllowlistToken);
+                }
             }
 
             synchronized (this) {
@@ -3371,13 +3360,9 @@
     private void writeToParcelImpl(Parcel parcel, int flags) {
         parcel.writeInt(1);
 
-        if (Flags.secureAllowlistToken()) {
-            // Always use the same token as the root notification (might be null).
-            IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class);
-            parcel.writeStrongBinder(rootNotificationToken);
-        } else {
-            parcel.writeStrongBinder(mAllowlistToken);
-        }
+        // Always use the same token as the root notification (might be null).
+        IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class);
+        parcel.writeStrongBinder(rootNotificationToken);
 
         parcel.writeLong(when);
         parcel.writeLong(creationTime);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 789c99d..1b29b7a 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -168,7 +168,11 @@
     /**
      * @hide
      */
-    public static final int MAX_VIBRATION_LENGTH = 1000;
+    public static final int MAX_VIBRATION_LENGTH = 500;
+    /**
+     * @hide
+     */
+    public static final int MAX_SERIALIZED_VIBRATION_LENGTH = 32_768;
 
     private static final String TAG_CHANNEL = "channel";
     private static final String ATT_NAME = "name";
@@ -368,6 +372,9 @@
         if (Flags.notificationChannelVibrationEffectApi()) {
             mVibrationEffect =
                     in.readInt() != 0 ? VibrationEffect.CREATOR.createFromParcel(in) : null;
+            if (Flags.notifChannelCropVibrationEffects() && mVibrationEffect != null) {
+                mVibrationEffect = getTrimmedVibrationEffect(mVibrationEffect);
+            }
         }
         mUserLockedFields = in.readInt();
         mUserVisibleTaskShown = in.readByte() != 0;
@@ -582,6 +589,23 @@
         return input;
     }
 
+    // Returns trimmed vibration effect or null if not trimmable.
+    private VibrationEffect getTrimmedVibrationEffect(VibrationEffect effect) {
+        if (effect == null) {
+            return null;
+        }
+        // trim if possible; check serialized length; reject if it is still too long
+        VibrationEffect result = effect;
+        VibrationEffect trimmed = effect.cropToLengthOrNull(MAX_VIBRATION_LENGTH);
+        if (trimmed != null) {
+            result = trimmed;
+        }
+        if (vibrationToString(result).length() > MAX_SERIALIZED_VIBRATION_LENGTH) {
+            return null;
+        }
+        return result;
+    }
+
     /**
      * @hide
      */
@@ -685,6 +709,11 @@
     public void setVibrationPattern(long[] vibrationPattern) {
         this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0;
         this.mVibrationPattern = vibrationPattern;
+        if (Flags.notifChannelCropVibrationEffects()) {
+            if (vibrationPattern != null && vibrationPattern.length > MAX_VIBRATION_LENGTH) {
+                this.mVibrationPattern = Arrays.copyOf(vibrationPattern, MAX_VIBRATION_LENGTH);
+            }
+        }
         if (Flags.notificationChannelVibrationEffectApi()) {
             try {
                 this.mVibrationEffect =
@@ -731,9 +760,21 @@
     public void setVibrationEffect(@Nullable VibrationEffect effect) {
         this.mVibrationEnabled = effect != null;
         this.mVibrationEffect = effect;
-        this.mVibrationPattern =
-                effect == null
-                ? null : effect.computeCreateWaveformOffOnTimingsOrNull();
+        if (Flags.notifChannelCropVibrationEffects() && effect != null) {
+            // Try converting to a vibration pattern and trimming that array. If not convertible
+            // to a pattern directly, try trimming the vibration effect if possible and storing
+            // that version instead.
+            long[] pattern = effect.computeCreateWaveformOffOnTimingsOrNull();
+            if (pattern != null) {
+                setVibrationPattern(pattern);
+            } else {
+                this.mVibrationEffect = getTrimmedVibrationEffect(mVibrationEffect);
+            }
+        } else {
+            this.mVibrationPattern =
+                    mVibrationEffect == null
+                            ? null : mVibrationEffect.computeCreateWaveformOffOnTimingsOrNull();
+        }
     }
 
     /**
@@ -1172,7 +1213,9 @@
             if (vibrationEffect != null) {
                 // Restore the effect only if it is not null. This allows to avoid undoing a
                 // `setVibrationPattern` call above, if that was done with a non-null pattern
-                // (e.g. back up from a version that did not support `setVibrationEffect`).
+                // (e.g. back up from a version that did not support `setVibrationEffect`), or
+                // when notif_channel_crop_vibration_effects is true, if there is an equivalent
+                // vibration pattern available.
                 setVibrationEffect(vibrationEffect);
             }
         }
@@ -1365,7 +1408,11 @@
             out.attribute(null, ATT_VIBRATION, longArrayToString(getVibrationPattern()));
         }
         if (getVibrationEffect() != null) {
-            out.attribute(null, ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect()));
+            if (!Flags.notifChannelCropVibrationEffects() || getVibrationPattern() == null) {
+                // When notif_channel_crop_vibration_effects is on, only serialize the vibration
+                // effect if we do not already have an equivalent vibration pattern.
+                out.attribute(null, ATT_VIBRATION_EFFECT, vibrationToString(getVibrationEffect()));
+            }
         }
         if (getUserLockedFields() != 0) {
             out.attributeInt(null, ATT_USER_LOCKED, getUserLockedFields());
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..e44e776 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -31,6 +31,7 @@
 import android.app.ambientcontext.AmbientContextManager;
 import android.app.ambientcontext.IAmbientContextManager;
 import android.app.appfunctions.AppFunctionManager;
+import android.app.appfunctions.AppFunctionManagerConfiguration;
 import android.app.appfunctions.IAppFunctionManager;
 import android.app.appsearch.AppSearchManagerFrameworkInitializer;
 import android.app.blob.BlobStoreManagerFrameworkInitializer;
@@ -48,6 +49,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;
@@ -935,8 +938,10 @@
                         @Override
                         public AppFunctionManager createService(ContextImpl ctx)
                                 throws ServiceNotFoundException {
+                            if (!AppFunctionManagerConfiguration.isSupported(ctx)) {
+                                return null;
+                            }
                             IAppFunctionManager service;
-                            //TODO(b/357551503): If the feature not present avoid look up every time
                             service = IAppFunctionManager.Stub.asInterface(
                                     ServiceManager.getServiceOrThrow(Context.APP_FUNCTION_SERVICE));
                             return new AppFunctionManager(service, ctx.getOuterContext());
@@ -1703,6 +1708,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/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index d9594d3..4c97dbb 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -93,3 +93,24 @@
      }
 }
 
+flag {
+     namespace: "backstage_power"
+     name: "rate_limit_get_processes_in_error_state"
+     description: "Rate limit calls to getProcessesInErrorState using a cache"
+     is_fixed_read_only: true
+     bug: "361146083"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "backstage_power"
+     name: "cache_get_current_user_id"
+     description: "Add caching for getCurrentUserId"
+     is_fixed_read_only: true
+     bug: "361853873"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java
index 02e492b..515c1c6 100644
--- a/core/java/android/app/admin/AccountTypePolicyKey.java
+++ b/core/java/android/app/admin/AccountTypePolicyKey.java
@@ -24,7 +24,6 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -54,9 +53,7 @@
     @TestApi
     public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) {
         super(key);
-        if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType");
-        }
+        PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType");
         mAccountType = Objects.requireNonNull((accountType));
     }
 
diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java
index c993671..00e67e6 100644
--- a/core/java/android/app/admin/BundlePolicyValue.java
+++ b/core/java/android/app/admin/BundlePolicyValue.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -31,9 +30,7 @@
 
     public BundlePolicyValue(Bundle value) {
         super(value);
-        if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            PolicySizeVerifier.enforceMaxBundleFieldsLength(value);
-        }
+        PolicySizeVerifier.enforceMaxBundleFieldsLength(value);
     }
 
     private BundlePolicyValue(Parcel source) {
diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java
index a7a2f7d..f092b7b 100644
--- a/core/java/android/app/admin/ComponentNamePolicyValue.java
+++ b/core/java/android/app/admin/ComponentNamePolicyValue.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.os.Parcel;
 
@@ -31,9 +30,7 @@
 
     public ComponentNamePolicyValue(@NonNull ComponentName value) {
         super(value);
-        if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            PolicySizeVerifier.enforceMaxComponentNameLength(value);
-        }
+        PolicySizeVerifier.enforceMaxComponentNameLength(value);
     }
 
     private ComponentNamePolicyValue(Parcel source) {
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..c0e435c 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -16,9 +16,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;
 import android.annotation.SystemApi;
@@ -50,7 +47,6 @@
     /**
      * String identifier for {@link DevicePolicyManager#setSecurityLoggingEnabled}.
      */
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     public static final String SECURITY_LOGGING_POLICY = "securityLogging";
 
     /**
@@ -58,7 +54,6 @@
      *
      * @hide
      */
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @SystemApi
     public static final String AUDIT_LOGGING_POLICY = "auditLogging";
 
@@ -188,13 +183,11 @@
     /**
      * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
      */
-    @FlaggedApi(FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
     public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
 
     /**
      * String identifier for {@link DevicePolicyManager#setRequiredPasswordComplexity}.
      */
-    @FlaggedApi(FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
     public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity";
 
     /**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 965e3c4..0f54cb7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -54,13 +54,8 @@
 import static android.Manifest.permission.SET_TIME;
 import static android.Manifest.permission.SET_TIME_ZONE;
 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 +2984,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
     public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17;
 
     /**
@@ -4334,11 +4328,13 @@
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true)
     @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    @UserHandleAware
     public @ContentProtectionPolicy int getContentProtectionPolicy(@Nullable ComponentName admin) {
         throwIfParentInstance("getContentProtectionPolicy");
         if (mService != null) {
             try {
-                return mService.getContentProtectionPolicy(admin, mContext.getPackageName());
+                return mService.getContentProtectionPolicy(admin, mContext.getPackageName(),
+                        myUserId());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -10480,10 +10476,6 @@
     @WorkerThread
     public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName,
             Bundle settings) {
-        if (!Flags.dmrhSetAppRestrictions()) {
-            throwIfParentInstance("setApplicationRestrictions");
-        }
-
         if (mService != null) {
             try {
                 mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName,
@@ -11888,9 +11880,6 @@
     @WorkerThread
     public @NonNull Bundle getApplicationRestrictions(
             @Nullable ComponentName admin, String packageName) {
-        if (!Flags.dmrhSetAppRestrictions()) {
-            throwIfParentInstance("getApplicationRestrictions");
-        }
 
         if (mService != null) {
             try {
@@ -14235,21 +14224,11 @@
      */
     public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
         throwIfParentInstance("getParentProfileInstance");
-        try {
-            if (Flags.dmrhSetAppRestrictions()) {
-                UserManager um = mContext.getSystemService(UserManager.class);
-                if (!um.isManagedProfile()) {
-                    throw new SecurityException("The current user does not have a parent profile.");
-                }
-            } else {
-                if (!mService.isManagedProfile(admin)) {
-                    throw new SecurityException("The current user does not have a parent profile.");
-                }
-            }
-            return new DevicePolicyManager(mContext, mService, true);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        UserManager um = mContext.getSystemService(UserManager.class);
+        if (!um.isManagedProfile()) {
+            throw new SecurityException("The current user does not have a parent profile.");
         }
+        return new DevicePolicyManager(mContext, mService, true);
     }
 
     /**
@@ -14333,7 +14312,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void setAuditLogEnabled(boolean enabled) {
         throwIfParentInstance("setAuditLogEnabled");
@@ -14350,7 +14328,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public boolean isAuditLogEnabled() {
         throwIfParentInstance("isAuditLogEnabled");
@@ -14372,7 +14349,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void setAuditLogEventCallback(
             @NonNull @CallbackExecutor Executor executor,
@@ -14399,7 +14375,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
     public void clearAuditLogEventCallback() {
         throwIfParentInstance("clearAuditLogEventCallback");
@@ -17747,7 +17722,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() {
@@ -17816,7 +17790,6 @@
      */
     @TestApi
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT)
-    @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED)
     public void forceSetMaxPolicyStorageLimit(int storageLimit) {
         if (mService != null) {
             try {
@@ -17834,7 +17807,6 @@
      */
     @TestApi
     @RequiresPermission(permission.MANAGE_DEVICE_POLICY_STORAGE_LIMIT)
-    @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED)
     public int getPolicySizeForAdmin(@NonNull EnforcingAdmin admin) {
         if (mService != null) {
             try {
@@ -17853,13 +17825,9 @@
      * @hide
      */
     @TestApi
-    @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_PROVISIONING_FIX_ENABLED)
     @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @DeviceAdminInfo.HeadlessDeviceOwnerMode
     public int getHeadlessDeviceOwnerMode() {
-        if (!Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
-            return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
-        }
         if (mService != null) {
             try {
                 return mService.getHeadlessDeviceOwnerMode(mContext.getPackageName());
diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java
index f70a53f..5f9bb9c 100644
--- a/core/java/android/app/admin/EnforcingAdmin.java
+++ b/core/java/android/app/admin/EnforcingAdmin.java
@@ -16,9 +16,6 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED;
-
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -64,7 +61,6 @@
      *
      * @hide
      */
-    @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_INTERNAL_BUG_FIX_ENABLED)
     @TestApi
     public EnforcingAdmin(
             @NonNull String packageName, @NonNull Authority authority,
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c393a9e..d4e5c99 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -621,7 +621,7 @@
     void calculateHasIncompatibleAccounts();
 
     void setContentProtectionPolicy(in ComponentName who, String callerPackageName, int policy);
-    int getContentProtectionPolicy(in ComponentName who, String callerPackageName);
+    int getContentProtectionPolicy(in ComponentName who, String callerPackageName, int userId);
 
     int[] getSubscriptionIds(String callerPackageName);
 
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
index 68b4ad8..ab32d46 100644
--- a/core/java/android/app/admin/LockTaskPolicy.java
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.app.admin.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -135,10 +134,8 @@
     }
 
     private void setPackagesInternal(Set<String> packages) {
-        if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            for (String p : packages) {
-                PolicySizeVerifier.enforceMaxPackageNameLength(p);
-            }
+        for (String p : packages) {
+            PolicySizeVerifier.enforceMaxPackageNameLength(p);
         }
         mPackages = new HashSet<>(packages);
     }
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
index 1a04f6c..226c576 100644
--- a/core/java/android/app/admin/PackagePermissionPolicyKey.java
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -25,7 +25,6 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -59,10 +58,8 @@
     public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName,
             @NonNull String permissionName) {
         super(identifier);
-        if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
-            PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName");
-        }
+        PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
+        PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName");
         mPackageName = Objects.requireNonNull((packageName));
         mPermissionName = Objects.requireNonNull((permissionName));
     }
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
index 9e31a23..8fa21db 100644
--- a/core/java/android/app/admin/PackagePolicyKey.java
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -24,7 +24,6 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -55,9 +54,7 @@
     @TestApi
     public PackagePolicyKey(@NonNull String key, @NonNull String packageName) {
         super(key);
-        if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
-        }
+        PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
         mPackageName = Objects.requireNonNull((packageName));
     }
 
diff --git a/core/java/android/app/admin/PackageSetPolicyValue.java b/core/java/android/app/admin/PackageSetPolicyValue.java
index 8b253a2..24c50b0 100644
--- a/core/java/android/app/admin/PackageSetPolicyValue.java
+++ b/core/java/android/app/admin/PackageSetPolicyValue.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.admin.flags.Flags;
 import android.os.Parcel;
 
 import java.util.HashSet;
@@ -32,10 +31,8 @@
 
     public PackageSetPolicyValue(@NonNull Set<String> value) {
         super(value);
-        if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            for (String packageName : value) {
-                PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
-            }
+        for (String packageName : value) {
+            PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
         }
     }
 
diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java
index 6efe9ad..bb07c23 100644
--- a/core/java/android/app/admin/StringPolicyValue.java
+++ b/core/java/android/app/admin/StringPolicyValue.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.admin.flags.Flags;
 import android.os.Parcel;
 
 import java.util.Objects;
@@ -30,9 +29,7 @@
 
     public StringPolicyValue(@NonNull String value) {
         super(value);
-        if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            PolicySizeVerifier.enforceMaxStringLength(value, "policyValue");
-        }
+        PolicySizeVerifier.enforceMaxStringLength(value, "policyValue");
     }
 
     private StringPolicyValue(Parcel source) {
diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java
index 9054287..16cfba4 100644
--- a/core/java/android/app/admin/UserRestrictionPolicyKey.java
+++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java
@@ -21,7 +21,6 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -45,9 +44,7 @@
     @TestApi
     public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) {
         super(identifier);
-        if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction");
-        }
+        PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction");
         mRestriction = Objects.requireNonNull(restriction);
     }
 
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 9148e3c..faa5a91 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -4,6 +4,7 @@
 package: "android.app.admin.flags"
 container: "system"
 
+# Fully rolled out and must not be used.
 flag {
   name: "policy_engine_migration_v2_enabled"
   is_exported: true
@@ -28,16 +29,6 @@
 }
 
 flag {
-  name: "device_policy_size_tracking_internal_bug_fix_enabled"
-  namespace: "enterprise"
-  description: "Bug fix for tracking the total policy size and have a max threshold"
-  bug: "281543351"
-  metadata {
-      purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "onboarding_bugreport_v2_enabled"
   is_exported: true
   namespace: "enterprise"
@@ -77,13 +68,6 @@
 }
 
 flag {
-  name: "permission_migration_for_zero_trust_impl_enabled"
-  namespace: "enterprise"
-  description: "(Implementation) Migrate existing APIs to permission based, and enable DMRH to call them to collect Zero Trust signals."
-  bug: "289520697"
-}
-
-flag {
   name: "device_theft_api_enabled"
   is_exported: true
   namespace: "enterprise"
@@ -106,6 +90,71 @@
 }
 
 flag {
+  name: "coexistence_migration_for_supervision_enabled"
+  is_exported: true
+  namespace: "enterprise"
+  description: "Migrate existing APIs that are used by supervision (Kids Module) to be coexistable."
+  bug: "356894721"
+}
+
+flag {
+ name: "reset_password_with_token_coexistence"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Enables coexistence support for resetPasswordWithToken and setResetPasswordToken."
+ bug: "359187209"
+}
+
+flag {
+ name: "set_keyguard_disabled_features_coexistence"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Enables coexistence support for setKeyguardDisabledFeatures."
+ bug: "359186276"
+}
+
+flag {
+ name: "set_application_restrictions_coexistence"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Enables coexistence support for setApplicationRestrictions."
+ bug: "359188153"
+}
+
+flag {
+ name: "set_auto_time_enabled_coexistence"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Enables coexistence support for setAutoTimeEnabled."
+ bug: "359188869"
+}
+
+flag {
+ name: "set_backup_service_enabled_coexistence"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Enables coexistence support for setBackupServiceEnabled."
+ bug: "359188483"
+}
+
+flag {
+ name: "set_auto_time_zone_enabled_coexistence"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Enables coexistence support for setAutoTimeZoneEnabled."
+ bug: "364338300"
+}
+
+flag {
+ name: "set_permission_grant_state_coexistence"
+ is_exported: true
+ namespace: "enterprise"
+ description: "Enables coexistence support for setPermissionGrantState."
+ bug: "364338410"
+}
+
+# Fully rolled out and must not be used.
+flag {
   name: "security_log_v2_enabled"
   is_exported: true
   namespace: "enterprise"
@@ -124,13 +173,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"
@@ -179,6 +221,7 @@
   bug: "295301164"
 }
 
+# Fully rolled out and must not be used.
 flag {
   name: "headless_device_owner_single_user_enabled"
   is_exported: true
@@ -206,26 +249,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"
@@ -243,33 +266,6 @@
 }
 
 flag {
-  name: "headless_device_owner_provisioning_fix_enabled"
-  namespace: "enterprise"
-  description: "Fix provisioning for single-user headless DO"
-  bug: "289515470"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
-  name: "dmrh_set_app_restrictions"
-  namespace: "enterprise"
-  description: "Allow DMRH to set application restrictions (both on the profile and the parent)"
-  bug: "328758346"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
-  name: "allow_screen_brightness_control_on_cope"
-  namespace: "enterprise"
-  description: "Allow COPE admin to control screen brightness and timeout."
-  bug: "323894620"
-}
-
-flag {
   name: "always_persist_do"
   namespace: "enterprise"
   description: "Always write device_owners2.xml so that migration flags aren't lost"
@@ -287,16 +283,6 @@
 }
 
 flag {
-  name: "headless_device_owner_delegate_security_logging_bug_fix"
-  namespace: "enterprise"
-  description: "Fix delegate security logging for single user headless DO."
-  bug: "289515470"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "headless_single_user_bad_device_admin_state_fix"
   namespace: "enterprise"
   description: "Fix the bad state in DPMS caused by an earlier bug related to the headless single user change"
@@ -317,16 +303,6 @@
 }
 
 flag {
-  name: "delete_private_space_under_restriction"
-  namespace: "enterprise"
-  description: "Delete private space if user restriction is set"
-  bug: "328758346"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
     name: "unmanaged_mode_migration"
     namespace: "enterprise"
     description: "Migrate APIs for unmanaged mode"
@@ -337,16 +313,6 @@
 }
 
 flag {
-    name: "headless_single_user_fixes"
-    namespace: "enterprise"
-    description: "Various fixes for headless single user mode"
-    bug: "289515470"
-    metadata {
-      purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "backup_connected_apps_settings"
     namespace: "enterprise"
     description: "backup and restore connected work and personal apps user settings across devices"
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index b6240a7..8f609de 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -49,9 +49,8 @@
     /**
      * Creates an instance.
      *
-     * @param mService An interface to the backing service.
-     * @param context A {@link Context}.
-     *
+     * @param service An interface to the backing service.
+     * @param context  A {@link Context}.
      * @hide
      */
     public AppFunctionManager(IAppFunctionManager service, Context context) {
@@ -108,8 +107,8 @@
                             } catch (RuntimeException e) {
                                 // Ideally shouldn't happen since errors are wrapped into the
                                 // response, but we catch it here for additional safety.
-                                callback.accept(new ExecuteAppFunctionResponse.Builder(
-                                        getResultCode(e), e.getMessage()).build());
+                                callback.accept(ExecuteAppFunctionResponse.newFailure(
+                                        getResultCode(e), e.getMessage(), /*extras=*/ null));
                             }
                         }
                     });
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerConfiguration.java b/core/java/android/app/appfunctions/AppFunctionManagerConfiguration.java
new file mode 100644
index 0000000..e4784b4
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionManagerConfiguration.java
@@ -0,0 +1,63 @@
+/*
+ * 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.enableAppFunctionManager;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+/**
+ * Represents the system configuration of support for the {@code AppFunctionManager} and
+ * associated systems.
+ *
+ * @hide
+ */
+public class AppFunctionManagerConfiguration {
+    private final Context mContext;
+
+    /**
+     * Constructs a new instance of {@code AppFunctionManagerConfiguration}.
+     * @param context context
+     */
+    public AppFunctionManagerConfiguration(@NonNull final Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Indicates whether the current target is intended to support {@code AppFunctionManager}.
+     * @return {@code true} if supported; otherwise {@code false}
+     */
+    public boolean isSupported() {
+        return enableAppFunctionManager() && !isWatch();
+
+    }
+
+    /**
+     * Indicates whether the current target is intended to support {@code AppFunctionManager}.
+     * @param context context
+     * @return {@code true} if supported; otherwise {@code false}
+     */
+    public static boolean isSupported(@NonNull final Context context) {
+        return new AppFunctionManagerConfiguration(context).isSupported();
+    }
+
+    private boolean isWatch() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+}
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/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 6259d16..22bc908 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -81,8 +81,9 @@
                         // Apps should handle exceptions. But if they don't, report the error on
                         // behalf of them.
                         safeCallback.onResult(
-                                new ExecuteAppFunctionResponse.Builder(
-                                        getResultCode(ex), getExceptionMessage(ex)).build());
+                                ExecuteAppFunctionResponse.newFailure(
+                                        getResultCode(ex),
+                                        ex.getMessage(), /*extras=*/  null));
                     }
                 }
             };
@@ -117,8 +118,4 @@
     public abstract void onExecuteFunction(
             @NonNull ExecuteAppFunctionRequest request,
             @NonNull Consumer<ExecuteAppFunctionResponse> callback);
-
-    private String getExceptionMessage(Exception exception) {
-        return exception.getMessage() == null ? "" : exception.getMessage();
-    }
 }
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/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index a50425e..db3de62 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -40,8 +40,8 @@
                 public ExecuteAppFunctionRequest createFromParcel(Parcel parcel) {
                     String targetPackageName = parcel.readString8();
                     String functionIdentifier = parcel.readString8();
-                    GenericDocument parameters;
-                    parameters = GenericDocument.createFromParcel(parcel);
+                    GenericDocumentWrapper parameters = GenericDocumentWrapper
+                            .CREATOR.createFromParcel(parcel);
                     Bundle extras = parcel.readBundle(Bundle.class.getClassLoader());
                     return new ExecuteAppFunctionRequest(
                             targetPackageName, functionIdentifier, extras, parameters);
@@ -75,17 +75,17 @@
      *
      * <p>The document may have missing parameters. Developers are advised to implement defensive
      * handling measures.
-     *
+     * <p>
      * TODO(b/357551503): Document how function parameters can be obtained for function execution
      */
     @NonNull
-    private final GenericDocument mParameters;
+    private final GenericDocumentWrapper mParameters;
 
     private ExecuteAppFunctionRequest(
             @NonNull String targetPackageName,
             @NonNull String functionIdentifier,
             @NonNull Bundle extras,
-            @NonNull GenericDocument parameters) {
+            @NonNull GenericDocumentWrapper parameters) {
         mTargetPackageName = Objects.requireNonNull(targetPackageName);
         mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
         mExtras = Objects.requireNonNull(extras);
@@ -117,7 +117,7 @@
      */
     @NonNull
     public GenericDocument getParameters() {
-        return mParameters;
+        return mParameters.getValue();
     }
 
     /**
@@ -152,7 +152,8 @@
         @NonNull
         private Bundle mExtras = Bundle.EMPTY;
         @NonNull
-        private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build();
+        private GenericDocument mParameters =
+                new GenericDocument.Builder<>("", "", "").build();
 
         public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) {
             mTargetPackageName = Objects.requireNonNull(targetPackageName);
@@ -173,7 +174,8 @@
          */
         @NonNull
         public Builder setParameters(@NonNull GenericDocument parameters) {
-            mParameters = Objects.requireNonNull(parameters);
+            Objects.requireNonNull(parameters);
+            mParameters = parameters;
             return this;
         }
 
@@ -183,7 +185,8 @@
         @NonNull
         public ExecuteAppFunctionRequest build() {
             return new ExecuteAppFunctionRequest(
-                    mTargetPackageName, mFunctionIdentifier, mExtras, mParameters);
+                    mTargetPackageName, mFunctionIdentifier, mExtras,
+                    new GenericDocumentWrapper(mParameters));
         }
     }
 }
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 872327d..58d4088 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -41,13 +41,16 @@
             new Creator<ExecuteAppFunctionResponse>() {
                 @Override
                 public ExecuteAppFunctionResponse createFromParcel(Parcel parcel) {
-                    GenericDocument result =
-                            Objects.requireNonNull(GenericDocument.createFromParcel(parcel));
+                    GenericDocumentWrapper resultWrapper =
+                            Objects.requireNonNull(
+                                    GenericDocumentWrapper
+                                            .CREATOR.createFromParcel(parcel));
                     Bundle extras = Objects.requireNonNull(
                             parcel.readBundle(Bundle.class.getClassLoader()));
                     int resultCode = parcel.readInt();
                     String errorMessage = parcel.readString8();
-                    return new ExecuteAppFunctionResponse(result, extras, resultCode, errorMessage);
+                    return new ExecuteAppFunctionResponse(
+                            resultWrapper, extras, resultCode, errorMessage);
                 }
 
                 @Override
@@ -127,7 +130,7 @@
      * <p>See {@link #getResultDocument} for more information on extracting the return value.
      */
     @NonNull
-    private final GenericDocument mResultDocument;
+    private final GenericDocumentWrapper mResultDocumentWrapper;
 
     /**
      * Returns the additional metadata data relevant to this function execution response.
@@ -135,17 +138,79 @@
     @NonNull
     private final Bundle mExtras;
 
-    private ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument,
+    private ExecuteAppFunctionResponse(@NonNull GenericDocumentWrapper resultDocumentWrapper,
                                        @NonNull Bundle extras,
                                        @ResultCode int resultCode,
                                        @Nullable String errorMessage) {
-        mResultDocument = Objects.requireNonNull(resultDocument);
+        mResultDocumentWrapper = Objects.requireNonNull(resultDocumentWrapper);
         mExtras = Objects.requireNonNull(extras);
         mResultCode = resultCode;
         mErrorMessage = errorMessage;
     }
 
     /**
+     * Returns result codes from throwable.
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+    static @ResultCode int getResultCode(@NonNull Throwable t) {
+        if (t instanceof IllegalArgumentException) {
+            return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+        }
+        return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
+    }
+
+    /**
+     * Returns a successful response.
+     *
+     * @param resultDocument The return value of the executed function.
+     * @param extras         The additional metadata data relevant to this function execution
+     *                       response.
+     */
+    @NonNull
+    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+    public static ExecuteAppFunctionResponse newSuccess(@NonNull GenericDocument resultDocument,
+                                                        @Nullable Bundle extras) {
+        Objects.requireNonNull(resultDocument);
+        Bundle actualExtras = getActualExtras(extras);
+        GenericDocumentWrapper resultDocumentWrapper = new GenericDocumentWrapper(resultDocument);
+
+        return new ExecuteAppFunctionResponse(
+                resultDocumentWrapper, actualExtras, RESULT_OK, /*errorMessage=*/null);
+    }
+
+    /**
+     * Returns a failure response.
+     *
+     * @param resultCode   The result code of the app function execution.
+     * @param extras       The additional metadata data relevant to this function execution
+     *                     response.
+     * @param errorMessage The error message associated with the result, if any.
+     */
+    @NonNull
+    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+    public static ExecuteAppFunctionResponse newFailure(@ResultCode int resultCode,
+                                                        @Nullable String errorMessage,
+                                                        @Nullable Bundle extras) {
+        if (resultCode == RESULT_OK) {
+            throw new IllegalArgumentException("resultCode must not be RESULT_OK");
+        }
+        Bundle actualExtras = getActualExtras(extras);
+        GenericDocumentWrapper emptyWrapper = new GenericDocumentWrapper(
+                new GenericDocument.Builder<>("", "", "").build());
+        return new ExecuteAppFunctionResponse(
+                emptyWrapper, actualExtras, resultCode, errorMessage);
+    }
+
+    private static Bundle getActualExtras(@Nullable Bundle extras) {
+        if (extras == null) {
+            return Bundle.EMPTY;
+        }
+        return extras;
+    }
+
+    /**
      * Returns a generic document containing the return value of the executed function.
      *
      * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.</p>
@@ -166,7 +231,7 @@
      */
     @NonNull
     public GenericDocument getResultDocument() {
-        return mResultDocument;
+        return mResultDocumentWrapper.getValue();
     }
 
     /**
@@ -210,7 +275,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        mResultDocument.writeToParcel(dest, flags);
+        mResultDocumentWrapper.writeToParcel(dest, flags);
         dest.writeBundle(mExtras);
         dest.writeInt(mResultCode);
         dest.writeString8(mErrorMessage);
@@ -234,73 +299,4 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {
     }
-
-    /**
-     * Returns result codes from throwable.
-     *
-     * @hide
-     */
-    static @ResultCode int getResultCode(@NonNull Throwable t) {
-        if (t instanceof IllegalArgumentException) {
-            return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
-        }
-        return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
-    }
-
-    /**
-     * The builder for creating {@link ExecuteAppFunctionResponse} instances.
-     */
-    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
-    public static final class Builder {
-        @NonNull
-        private GenericDocument mResultDocument = new GenericDocument.Builder<>("", "", "").build();
-        @NonNull
-        private Bundle mExtras = Bundle.EMPTY;
-        private int mResultCode;
-        @Nullable
-        private String mErrorMessage;
-
-        /**
-         * Creates a new builder for {@link ExecuteAppFunctionResponse}.
-         */
-        private Builder() {
-        }
-
-        /**
-         * Creates a new builder for {@link ExecuteAppFunctionResponse} to build a success response
-         * with a result code of {@link #RESULT_OK} and a resultDocument.
-         */
-        public Builder(@NonNull GenericDocument resultDocument) {
-            mResultDocument = Objects.requireNonNull(resultDocument);
-            mResultCode = RESULT_OK;
-        }
-
-        /**
-         * Creates a new builder for {@link ExecuteAppFunctionResponse} to build an error response
-         * with a result code and an error message.
-         */
-        public Builder(@ResultCode int resultCode,
-                       @NonNull String errorMessage) {
-            mResultCode = resultCode;
-            mErrorMessage = Objects.requireNonNull(errorMessage);
-        }
-
-        /**
-         * Sets the extras of the app function execution.
-         */
-        @NonNull
-        public Builder setExtras(@NonNull Bundle extras) {
-            mExtras = Objects.requireNonNull(extras);
-            return this;
-        }
-
-        /**
-         * Builds the {@link ExecuteAppFunctionResponse} instance.
-         */
-        @NonNull
-        public ExecuteAppFunctionResponse build() {
-            return new ExecuteAppFunctionResponse(
-                    mResultDocument, mExtras, mResultCode, mErrorMessage);
-        }
-    }
 }
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
new file mode 100644
index 0000000..8c76c8e
--- /dev/null
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import android.app.appsearch.GenericDocument;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * The Parcelable object contains a {@link GenericDocument} to allow the parcelization of it
+ * exceeding the binder limit.
+ *
+ * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder
+ * directly or Android shared memory if the data is large.
+ *
+ * @hide
+ * @see Parcel#writeBlob(byte[])
+ */
+public final class GenericDocumentWrapper implements Parcelable {
+    public static final Creator<GenericDocumentWrapper> CREATOR =
+            new Creator<>() {
+                @Override
+                public GenericDocumentWrapper createFromParcel(Parcel in) {
+                    byte[] dataBlob = Objects.requireNonNull(in.readBlob());
+                    Parcel unmarshallParcel = Parcel.obtain();
+                    try {
+                        unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length);
+                        unmarshallParcel.setDataPosition(0);
+                        return new GenericDocumentWrapper(
+                                GenericDocument.createFromParcel(unmarshallParcel));
+                    } finally {
+                        unmarshallParcel.recycle();
+                    }
+                }
+
+                @Override
+                public GenericDocumentWrapper[] newArray(int size) {
+                    return new GenericDocumentWrapper[size];
+                }
+            };
+    @NonNull
+    private final GenericDocument mGenericDocument;
+
+    public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) {
+        mGenericDocument = Objects.requireNonNull(genericDocument);
+    }
+
+    /**
+     * Returns the wrapped {@link android.app.appsearch.GenericDocument}
+     */
+    @NonNull
+    public GenericDocument getValue() {
+        return mGenericDocument;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            mGenericDocument.writeToParcel(parcel, flags);
+            byte[] bytes = parcel.marshall();
+            dest.writeBlob(bytes);
+        } finally {
+            parcel.recycle();
+        }
+
+    }
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 606ca33..9891e89 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -128,6 +128,16 @@
 }
 
 flag {
+  name: "notif_channel_crop_vibration_effects"
+  namespace: "systemui"
+  description: "Limits the size of vibration effects that can be stored in a NotificationChannel"
+  bug: "345881518"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "evenly_divided_call_style_action_layout"
   namespace: "systemui"
   description: "Evenly divides horizontal space for action buttons in CallStyle notifications."
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl
similarity index 63%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
copy to core/java/android/app/supervision/ISupervisionManager.aidl
index 3c5beeb..8d25cad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/core/java/android/app/supervision/ISupervisionManager.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2024 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     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,
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package android.app.supervision;
 
-parcelable BubbleBarLocation;
\ No newline at end of file
+/**
+ * Internal IPC interface to the supervision service.
+ * {@hide}
+ */
+interface ISupervisionManager {
+    boolean isSupervisionEnabled();
+}
diff --git a/core/java/android/app/supervision/OWNERS b/core/java/android/app/supervision/OWNERS
new file mode 100644
index 0000000..afc5495
--- /dev/null
+++ b/core/java/android/app/supervision/OWNERS
@@ -0,0 +1,2 @@
+jparks@google.com
+romkal@google.com
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/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 4e0379e..7117f25 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -57,3 +57,10 @@
   description: "Enable support for persisting RemoteViews previews to Protobuf"
   bug: "306546610"
 }
+
+flag {
+  name: "remote_document_support"
+  namespace: "app_widgets"
+  description: "Remote document support features in Q2 2025 release"
+  bug: "339721781"
+}
\ No newline at end of file
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1529842..1cdf3b1 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -21,7 +21,6 @@
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
 
-import static java.util.Collections.unmodifiableMap;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
@@ -58,7 +57,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
-import android.util.ArrayMap;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -78,7 +76,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.BiConsumer;
@@ -146,12 +143,19 @@
     /**
      * The result code to propagate back to the user activity, indicates the internal error
      * in CompanionDeviceManager.
-     * E.g. Missing necessary permissions or duplicate {@link AssociationRequest}s when create the
-     * {@link AssociationInfo}.
      */
     public static final int RESULT_INTERNAL_ERROR = 3;
 
     /**
+     * The result code to propagate back to the user activity and
+     * {@link Callback#onFailure(int, CharSequence)}, indicates app is not allow to create the
+     * association due to the security issue.
+     * E.g. There are missing necessary permissions when creating association.
+     */
+    @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
+    public static final int RESULT_SECURITY_ERROR = 4;
+
+    /**
      * Requesting applications will receive the String in {@link Callback#onFailure} if the
      * association dialog is explicitly declined by the users. E.g. press the Don't allow
      * button.
@@ -374,7 +378,6 @@
          */
         public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {}
 
-        //TODO(b/331459560): Add deprecated and remove abstract after API cut for W.
         /**
          * Invoked if the association could not be created.
          *
@@ -385,11 +388,15 @@
         /**
          * Invoked if the association could not be created.
          *
-         * @param resultCode indicate the particular reason why the association
-         *                   could not be created.
+         * Please note that both {@link #onFailure(CharSequence error)} and this
+         * API will be called if the association could not be created.
+         *
+         * @param errorCode indicate the particular error code why the association
+         *                  could not be created.
+         * @param error error message.
          */
         @FlaggedApi(Flags.FLAG_ASSOCIATION_FAILURE_CODE)
-        public void onFailure(@ResultCode int resultCode) {}
+        public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {}
     }
 
     private final ICompanionDeviceManager mService;
@@ -1825,12 +1832,12 @@
         }
 
         @Override
-        public void onFailure(@ResultCode int resultCode) {
+        public void onFailure(@ResultCode int errorCode, @Nullable CharSequence error) {
             if (Flags.associationFailureCode()) {
-                execute(mCallback::onFailure, resultCode);
+                execute(mCallback::onFailure, errorCode, error);
             }
 
-            execute(mCallback::onFailure, RESULT_CODE_TO_REASON.get(resultCode));
+            execute(mCallback::onFailure, error);
         }
 
         private <T> void execute(Consumer<T> callback, T arg) {
@@ -1840,6 +1847,12 @@
                 mHandler.post(() -> callback.accept(arg));
             }
         }
+
+        private <T, U> void execute(BiConsumer<T, U> callback, T arg1, U arg2) {
+            if (mExecutor != null) {
+                mExecutor.execute(() -> callback.accept(arg1, arg2));
+            }
+        }
     }
 
     private static class OnAssociationsChangedListenerProxy
@@ -2014,15 +2027,4 @@
             }
         }
     }
-
-    private static final Map<Integer, String> RESULT_CODE_TO_REASON;
-    static {
-        final Map<Integer, String> map = new ArrayMap<>();
-        map.put(RESULT_CANCELED, REASON_CANCELED);
-        map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
-        map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
-        map.put(RESULT_INTERNAL_ERROR, REASON_INTERNAL_ERROR);
-
-        RESULT_CODE_TO_REASON = unmodifiableMap(map);
-    }
 }
diff --git a/core/java/android/companion/IAssociationRequestCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl
index b1be30a..a6f86a5 100644
--- a/core/java/android/companion/IAssociationRequestCallback.aidl
+++ b/core/java/android/companion/IAssociationRequestCallback.aidl
@@ -25,5 +25,5 @@
 
     oneway void onAssociationCreated(in AssociationInfo associationInfo);
 
-    oneway void onFailure(in int resultCode);
+    oneway void onFailure(in int errorCode, in CharSequence error);
 }
\ 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/ActivityPolicyExemption.java b/core/java/android/companion/virtual/ActivityPolicyExemption.java
index c81bb43..dc285d4 100644
--- a/core/java/android/companion/virtual/ActivityPolicyExemption.java
+++ b/core/java/android/companion/virtual/ActivityPolicyExemption.java
@@ -118,6 +118,7 @@
     /**
      * Builder for {@link ActivityPolicyExemption}.
      */
+    @FlaggedApi(Flags.FLAG_ACTIVITY_CONTROL_API)
     public static final class Builder {
 
         private @Nullable ComponentName mComponentName;
diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
index 7c674f9..767f52a 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -54,4 +54,13 @@
      */
     void onActivityLaunchBlocked(int displayId, in ComponentName componentName, in UserHandle user,
             in IntentSender intentSender);
+
+    /**
+     * Called when a secure surface is shown on the device.
+     *
+     * @param displayId The display ID on which the secure surface was shown.
+     * @param componentName The component name of the activity that showed the secure surface.
+     * @param user The user associated with the activity.
+     */
+    void onSecureWindowShown(int displayId, in ComponentName componentName, in UserHandle user);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index b7bf2d1..de20a68 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -151,7 +151,24 @@
                         Binder.restoreCallingIdentity(token);
                     }
                 }
+
+                @Override
+                public void onSecureWindowShown(int displayId, ComponentName componentName,
+                        UserHandle user) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        synchronized (mActivityListenersLock) {
+                            for (int i = 0; i < mActivityListeners.size(); i++) {
+                                mActivityListeners.valueAt(i)
+                                        .onSecureWindowShown(displayId, componentName, user);
+                            }
+                        }
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
             };
+
     private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
             new IVirtualDeviceSoundEffectListener.Stub() {
                 @Override
@@ -584,6 +601,12 @@
                     mActivityListener.onActivityLaunchBlocked(
                             displayId, componentName, user, intentSender));
         }
+
+        public void onSecureWindowShown(int displayId, ComponentName componentName,
+                UserHandle user) {
+            mExecutor.execute(() ->
+                    mActivityListener.onSecureWindowShown(displayId, componentName, user));
+        }
     }
 
     /**
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 40aa6837..cf34452 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -1255,6 +1255,20 @@
         @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
         default void onActivityLaunchBlocked(int displayId, @NonNull ComponentName componentName,
                 @NonNull UserHandle user, @Nullable IntentSender intentSender) {}
+
+        /**
+         * Called when a window with a secure surface is shown on the device.
+         *
+         * @param displayId The display ID on which the window was shown.
+         * @param componentName The component name of the activity that showed the window.
+         * @param user The user associated with the activity.
+         *
+         * @see Display#FLAG_SECURE
+         * @see WindowManager.LayoutParams#FLAG_SECURE
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+        default void onSecureWindowShown(int displayId, @NonNull ComponentName componentName,
+                @NonNull UserHandle user) {}
     }
 
     /**
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index c3c3f0e..297fe8a 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,17 @@
   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"
+}
+
+flag {
+  name: "status_bar_and_insets"
+  namespace: "virtual_devices"
+  description: "Allow for status bar and insets on virtual devices"
+  bug: "350007866"
+}
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..12c5d07 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -33,6 +33,7 @@
 import android.annotation.Nullable;
 import android.annotation.PermissionMethod;
 import android.annotation.PermissionName;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.StringDef;
 import android.annotation.StringRes;
@@ -106,6 +107,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;
@@ -6677,6 +6679,8 @@
      * Use with {@link #getSystemService(String)} to retrieve a {@link
      * android.webkit.WebViewUpdateManager} for accessing the WebView update service.
      *
+     * <p>This can only be used on devices with {@link PackageManager#FEATURE_WEBVIEW}.
+     *
      * @see #getSystemService(String)
      * @see android.webkit.WebViewUpdateManager
      * @hide
@@ -6684,6 +6688,7 @@
     @FlaggedApi(android.webkit.Flags.FLAG_UPDATE_SERVICE_IPC_WRAPPER)
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @SuppressLint("ServiceName")
+    @RequiresFeature(PackageManager.FEATURE_WEBVIEW)
     public static final String WEBVIEW_UPDATE_SERVICE = "webviewupdate";
 
     /**
@@ -6701,13 +6706,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/Intent.java b/core/java/android/content/Intent.java
index cb57c7b..da3cc1b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3767,7 +3767,7 @@
      * <p>The Intent will have the following extra value:</p>
      * <ul>
      *   <li><em>{@link android.content.Intent#EXTRA_PHONE_NUMBER}</em> -
-     *       the phone number originally intended to be dialed.</li>
+     *       the phone number dialed.</li>
      * </ul>
      * <p class="note">Starting in Android 15, this broadcast is no longer sent as an ordered
      * broadcast.  The <code>resultData</code> no longer has any effect and will not determine the
@@ -3800,6 +3800,14 @@
      * {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS}
      * permission to receive this Intent.</p>
      *
+     * <p class="note">Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this broadcast is
+     * no longer sent as an ordered broadcast, and does not allow activity launches.  This means
+     * that receivers may no longer change the phone number for the outgoing call, or cancel the
+     * outgoing call.  This functionality is only possible using the
+     * {@link android.telecom.CallRedirectionService} API.  Although background receivers are
+     * woken up to handle this intent, no guarantee is made as to the timeliness of the broadcast.
+     * </p>
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      *
@@ -7642,13 +7650,6 @@
             | FLAG_GRANT_PREFIX_URI_PERMISSION;
 
     /**
-     * Flags that are not normally set by application code, but set for you by the system.
-     */
-    private static final int SYSTEM_ONLY_FLAGS = FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
-            | FLAG_ACTIVITY_BROUGHT_TO_FRONT
-            | FLAG_RECEIVER_FROM_SHELL;
-
-    /**
      * Local flag indicating this instance was created by copy constructor.
      */
     private static final int LOCAL_FLAG_FROM_COPY = 1 << 0;
@@ -7701,11 +7702,6 @@
     @TestApi
     public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1 << 0;
 
-    /**
-     * Extended flags that are not normally set by application code, but set for you by the system.
-     */
-    private static final int SYSTEM_ONLY_EXTENDED_FLAGS = EXTENDED_FLAG_FILTER_MISMATCH;
-
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // toUri() and parseUri() options.
@@ -12649,28 +12645,6 @@
         }
     }
 
-    /**
-     * Prepare this {@link Intent} to enter system_server.
-     *
-     * @hide
-     */
-    public void prepareToEnterSystemServer() {
-        // Refuse possible leaked file descriptors
-        if (hasFileDescriptors()) {
-            throw new IllegalArgumentException("File descriptors passed in Intent");
-        }
-        // These flags are set only by the system, and should be stripped out as soon as the intent
-        // is received by system_server from the caller so it can be properly updated later.
-        removeFlags(SYSTEM_ONLY_FLAGS);
-        removeExtendedFlags(SYSTEM_ONLY_EXTENDED_FLAGS);
-        if (mOriginalIntent != null) {
-            mOriginalIntent.prepareToEnterSystemServer();
-        }
-        if (mSelector != null) {
-            mSelector.prepareToEnterSystemServer();
-        }
-    }
-
     /** @hide */
     public boolean hasWebURI() {
         if (getData() == null) {
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index cd3ce87..5779a44 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -2501,33 +2501,19 @@
         return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
     }
 
-    private void addIndentOrComma(StringBuilder sb, String indent) {
-        if (indent != null) {
-            sb.append("\n  ");
-            sb.append(indent);
-        } else {
-            sb.append(", ");
-        }
+    /** @hide */
+    public String toSimpleString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(mId);
+        addReadableFlags(sb);
+        return sb.toString();
     }
 
-    private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
-        final StringBuilder sb = new StringBuilder();
-
-        if (indent != null) {
-            sb.append(indent);
-        }
-
-        sb.append("ShortcutInfo {");
-
-        sb.append("id=");
-        sb.append(secure ? "***" : mId);
-
-        sb.append(", flags=0x");
-        sb.append(Integer.toHexString(mFlags));
+    private void addReadableFlags(StringBuilder sb) {
         sb.append(" [");
         if ((mFlags & FLAG_SHADOW) != 0) {
-            // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
-            // we don't have an isXxx for this.
+            // Note the shadow flag isn't actually used anywhere and it's
+            // just for dumpsys, so we don't have an isXxx for this.
             sb.append("Sdw");
         }
         if (!isEnabled()) {
@@ -2576,7 +2562,32 @@
             sb.append("Hid-L");
         }
         sb.append("]");
+    }
 
+    private void addIndentOrComma(StringBuilder sb, String indent) {
+        if (indent != null) {
+            sb.append("\n  ");
+            sb.append(indent);
+        } else {
+            sb.append(", ");
+        }
+    }
+
+    private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
+        final StringBuilder sb = new StringBuilder();
+
+        if (indent != null) {
+            sb.append(indent);
+        }
+
+        sb.append("ShortcutInfo {");
+
+        sb.append("id=");
+        sb.append(secure ? "***" : mId);
+
+        sb.append(", flags=0x");
+        sb.append(Integer.toHexString(mFlags));
+        addReadableFlags(sb);
         addIndentOrComma(sb, indent);
 
         sb.append("packageName=");
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 3a33ef9..28534ad 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -161,6 +161,16 @@
 }
 
 flag {
+    name: "fix_avatar_content_provider_null_authority"
+    namespace: "multiuser"
+    description: "Fix crash when content provider authority is null."
+    bug: "362880068"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
     name: "fix_avatar_picker_not_responding_for_new_user"
     namespace: "multiuser"
     description: "Avatar picker is not responding after selecting photo for new user."
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/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index d128055..a20159d 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -19,8 +19,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.annotation.TestApi;
-import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Parcel;
@@ -136,11 +134,8 @@
      * Get rollback impact level. Refer {@link
      * android.content.pm.PackageInstaller.SessionParams#setRollbackImpactLevel(int)} for more info
      * on impact level.
-     *
-     * @hide
      */
-    @TestApi
-    @FlaggedApi(Flags.FLAG_RECOVERABILITY_DETECTION)
+    @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY)
     public @PackageManager.RollbackImpactLevel int getRollbackImpactLevel() {
         return mRollbackImpactLevel;
     }
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/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index fc72db3..6682b36 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -61,7 +61,6 @@
             BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
             BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
             BIOMETRIC_ERROR_RE_ENROLL,
-            BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
             FINGERPRINT_ERROR_UNKNOWN,
             FINGERPRINT_ERROR_BAD_CALIBRATION,
             BIOMETRIC_ERROR_POWER_PRESSED})
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/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 7665fe8..04a810a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -461,6 +461,12 @@
         @SuppressLint("NonUserGetterCalled")
         public boolean registerClient(Context ctx, IBinder token, int extension,
                 String cameraId, Map<String, CameraMetadataNative> characteristicsMapNative) {
+            if (!SystemProperties.getBoolean("ro.camerax.extensions.enabled",
+                    /*default*/ false)) {
+                Log.v(TAG, "Disabled camera extension property!");
+                return false;
+            }
+
             boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/);
 
             if (Flags.concertMode()) {
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index fbed50a..ac72116 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1537,9 +1537,14 @@
      * be made, and for firing pre-capture flash pulses to estimate
      * scene brightness and required final capture flash power, when
      * the flash is enabled.</p>
-     * <p>Normally, this entry should be set to START for only a
-     * single request, and the application should wait until the
-     * sequence completes before starting a new one.</p>
+     * <p>Flash is enabled during precapture sequence when:</p>
+     * <ul>
+     * <li>AE mode is ON_ALWAYS_FLASH</li>
+     * <li>AE mode is ON_AUTO_FLASH and the scene is deemed too dark without flash, or</li>
+     * <li>AE mode is ON and flash mode is TORCH or SINGLE</li>
+     * </ul>
+     * <p>Normally, this entry should be set to START for only single request, and the
+     * application should wait until the sequence completes before starting a new one.</p>
      * <p>When a precapture metering sequence is finished, the camera device
      * may lock the auto-exposure routine internally to be able to accurately expose the
      * subsequent still capture image (<code>{@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE</code>).
@@ -2705,6 +2710,13 @@
      * in {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL android.flash.singleStrengthDefaultLevel}.
      * If {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to any of <code>ON_AUTO_FLASH</code>, <code>ON_ALWAYS_FLASH</code>,
      * <code>ON_AUTO_FLASH_REDEYE</code>, <code>ON_EXTERNAL_FLASH</code> values, then the strengthLevel will be ignored.</p>
+     * <p>When AE mode is ON and flash mode is TORCH or SINGLE, the application should make sure
+     * the AE mode, flash mode, and flash strength level remain the same between precapture
+     * trigger request and final capture request. The flash strength level being set during
+     * precapture sequence is used by the camera device as a reference. The actual strength
+     * may be less, and the auto-exposure routine makes sure proper conversions of sensor
+     * exposure time and sensitivities between precapture and final capture for the specified
+     * strength level.</p>
      * <p><b>Range of valid values:</b><br>
      * <code>[1-{@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
      * set to TORCH;
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index d652b4c..34ce92c 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -935,9 +935,14 @@
      * be made, and for firing pre-capture flash pulses to estimate
      * scene brightness and required final capture flash power, when
      * the flash is enabled.</p>
-     * <p>Normally, this entry should be set to START for only a
-     * single request, and the application should wait until the
-     * sequence completes before starting a new one.</p>
+     * <p>Flash is enabled during precapture sequence when:</p>
+     * <ul>
+     * <li>AE mode is ON_ALWAYS_FLASH</li>
+     * <li>AE mode is ON_AUTO_FLASH and the scene is deemed too dark without flash, or</li>
+     * <li>AE mode is ON and flash mode is TORCH or SINGLE</li>
+     * </ul>
+     * <p>Normally, this entry should be set to START for only single request, and the
+     * application should wait until the sequence completes before starting a new one.</p>
      * <p>When a precapture metering sequence is finished, the camera device
      * may lock the auto-exposure routine internally to be able to accurately expose the
      * subsequent still capture image (<code>{@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} == STILL_CAPTURE</code>).
@@ -2821,8 +2826,6 @@
      * boost when the light level threshold is exceeded.</p>
      * <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can
      * indicate when it is not being applied by returning 'INACTIVE'.</p>
-     * <p>This key will be absent from the CaptureResult if AE mode is not set to
-     * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.</p>
      * <p>The default value will always be 'INACTIVE'.</p>
      * <p><b>Possible values:</b></p>
      * <ul>
@@ -2996,6 +2999,13 @@
      * in {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL android.flash.singleStrengthDefaultLevel}.
      * If {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to any of <code>ON_AUTO_FLASH</code>, <code>ON_ALWAYS_FLASH</code>,
      * <code>ON_AUTO_FLASH_REDEYE</code>, <code>ON_EXTERNAL_FLASH</code> values, then the strengthLevel will be ignored.</p>
+     * <p>When AE mode is ON and flash mode is TORCH or SINGLE, the application should make sure
+     * the AE mode, flash mode, and flash strength level remain the same between precapture
+     * trigger request and final capture request. The flash strength level being set during
+     * precapture sequence is used by the camera device as a reference. The actual strength
+     * may be less, and the auto-exposure routine makes sure proper conversions of sensor
+     * exposure time and sensitivities between precapture and final capture for the specified
+     * strength level.</p>
      * <p><b>Range of valid values:</b><br>
      * <code>[1-{@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
      * set to TORCH;
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 48d2785..c7dba6c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -91,6 +91,17 @@
     private final String TAG;
     private final boolean DEBUG = false;
 
+    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+        private static final ThreadFactory mFactory = Executors.defaultThreadFactory();
+
+        @Override
+        public Thread newThread(Runnable r) {
+            Thread thread = mFactory.newThread(r);
+            thread.setName("CameraDeviceExecutor");
+            return thread;
+        }
+    };
+
     private static final int REQUEST_ID_NONE = -1;
 
     /**
@@ -355,14 +366,11 @@
         mCameraId = cameraId;
         if (Flags.singleThreadExecutor()) {
             mDeviceCallback = new ClientStateCallback(executor, callback);
-            mDeviceExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
-                @Override
-                public Thread newThread(Runnable r) {
-                    Thread thread = Executors.defaultThreadFactory().newThread(r);
-                    thread.setName("CameraDeviceExecutor");
-                    return thread;
-                }
-            });
+            if (Flags.singleThreadExecutorNaming()) {
+                mDeviceExecutor = Executors.newSingleThreadExecutor(sThreadFactory);
+            } else {
+                mDeviceExecutor = Executors.newSingleThreadExecutor();
+            }
         } else {
             mDeviceCallback = callback;
             mDeviceExecutor = executor;
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 109b0a8..6a96a54 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -158,6 +158,8 @@
                 return "thermal";
             case BRIGHTNESS_MAX_REASON_POWER_IC:
                 return "power IC";
+            case BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE:
+                return "wear bedtime";
         }
         return "invalid";
     }
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/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 03cf7c5..2a36238 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -567,7 +567,7 @@
         Objects.requireNonNull(listener, "listener must not be null");
 
         synchronized (mOnTabletModeChangedListeners) {
-            if (mOnTabletModeChangedListeners == null) {
+            if (mOnTabletModeChangedListeners.isEmpty()) {
                 initializeTabletModeListenerLocked();
             }
             int idx = findOnTabletModeChangedListenerLocked(listener);
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index c5d0caf22..8592ded 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -20,10 +20,12 @@
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS;
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
+import static com.android.hardware.input.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
 import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
+import static com.android.hardware.input.Flags.keyboardRepeatKeys;
 import static com.android.hardware.input.Flags.touchpadTapDragging;
 import static com.android.hardware.input.Flags.touchpadVisualizer;
 import static com.android.input.flags.Flags.enableInputFilterRustImpl;
@@ -40,6 +42,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.sysprop.InputProperties;
+import android.view.ViewConfiguration;
 
 /**
  * InputSettings encapsulates reading and writing settings related to input
@@ -90,6 +93,30 @@
      */
     public static final int DEFAULT_STYLUS_POINTER_ICON_ENABLED = 1;
 
+    /**
+     * The minimum allowed repeat keys timeout before starting key repeats.
+     * @hide
+     */
+    public static final int MIN_KEY_REPEAT_TIMEOUT_MILLIS = 150;
+
+    /**
+     * The maximum allowed repeat keys timeout before starting key repeats.
+     * @hide
+     */
+    public static final int MAX_KEY_REPEAT_TIMEOUT_MILLIS = 2000;
+
+    /**
+     * The minimum allowed repeat keys delay between successive key repeats.
+     * @hide
+     */
+    public static final int MIN_KEY_REPEAT_DELAY_MILLIS = 20;
+
+    /**
+     * The maximum allowed repeat keys delay between successive key repeats.
+     * @hide
+     */
+    public static final int MAX_KEY_REPEAT_DELAY_MILLIS = 2000;
+
     private InputSettings() {
     }
 
@@ -767,4 +794,141 @@
                 Settings.Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, enabled ? 1 : 0,
                 UserHandle.USER_CURRENT);
     }
+
+    /**
+     * Whether "Repeat keys" feature flag is enabled.
+     *
+     * <p>
+     * ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
+     * key on the physical keyboard is held down. This accessibility feature allows the user
+     * to configure the timeout before the key repeats begin as well as the delay
+     * between successive key repeats.
+     * </p>
+     *
+     * @hide
+     */
+    public static boolean isRepeatKeysFeatureFlagEnabled() {
+        return keyboardRepeatKeys();
+    }
+
+    /**
+     * Get Accessibility repeat keys timeout duration in milliseconds.
+     * The default key repeat timeout is {@link ViewConfiguration#DEFAULT_KEY_REPEAT_TIMEOUT_MS}.
+     *
+     * @param context The application context
+     * @return The time duration for which a key should be pressed after
+     *         which the pressed key will be repeated. The timeout must be between
+     *         {@link #MIN_KEY_REPEAT_TIMEOUT_MILLIS} and
+     *         {@link #MAX_KEY_REPEAT_TIMEOUT_MILLIS}
+     *
+     * <p>
+     * ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
+     * key on the physical keyboard is held down. This accessibility feature allows the user
+     * to configure the timeout before the key repeats begin as well as the delay
+     * between successive key repeats.
+     * </p>
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
+    public static int getAccessibilityRepeatKeysTimeout(@NonNull Context context) {
+        return Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.KEY_REPEAT_TIMEOUT_MS, ViewConfiguration.getKeyRepeatTimeout(),
+                UserHandle.USER_CURRENT);
+    }
+
+    /**
+     * Get Accessibility repeat keys delay rate in milliseconds.
+     * The default key repeat delay is {@link ViewConfiguration#DEFAULT_KEY_REPEAT_DELAY_MS}.
+     *
+     * @param context The application context
+     * @return Time duration between successive key repeats when a key is
+     *         pressed down. The delay duration must be between
+     *         {@link #MIN_KEY_REPEAT_DELAY_MILLIS} and
+     *         {@link #MAX_KEY_REPEAT_DELAY_MILLIS}
+     *
+     * <p>
+     * ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
+     * key on the physical keyboard is held down. This accessibility feature allows the user
+     * to configure the timeout before the key repeats begin as well as the delay
+     * between successive key repeats.
+     * </p>
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
+    public static int getAccessibilityRepeatKeysDelay(@NonNull Context context) {
+        return Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.KEY_REPEAT_DELAY_MS, ViewConfiguration.getKeyRepeatDelay(),
+                UserHandle.USER_CURRENT);
+    }
+
+    /**
+     * Set Accessibility repeat keys timeout duration in milliseconds.
+     *
+     * @param timeoutTimeMillis time duration for which a key should be pressed after which the
+     *                          pressed key will be repeated. The timeout must be between
+     *                          {@link #MIN_KEY_REPEAT_TIMEOUT_MILLIS} and
+     *                          {@link #MAX_KEY_REPEAT_TIMEOUT_MILLIS}
+     *
+     *  <p>
+     * ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
+     * key on the physical keyboard is held down. This accessibility feature allows the user
+     * to configure the timeout before the key repeats begin as well as the delay
+     *  between successive key repeats.
+     * </p>
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setAccessibilityRepeatKeysTimeout(@NonNull Context context,
+            int timeoutTimeMillis) {
+        if (timeoutTimeMillis < MIN_KEY_REPEAT_TIMEOUT_MILLIS
+                || timeoutTimeMillis > MAX_KEY_REPEAT_TIMEOUT_MILLIS) {
+            throw new IllegalArgumentException(
+                    "Provided repeat keys timeout should be in range ("
+                            + MIN_KEY_REPEAT_TIMEOUT_MILLIS + ","
+                            + MAX_KEY_REPEAT_TIMEOUT_MILLIS + ")");
+        }
+        Settings.Secure.putIntForUser(context.getContentResolver(),
+                Settings.Secure.KEY_REPEAT_TIMEOUT_MS, timeoutTimeMillis,
+                UserHandle.USER_CURRENT);
+    }
+
+    /**
+     * Set Accessibility repeat key delay duration in milliseconds.
+     *
+     * @param delayTimeMillis Time duration between successive key repeats when a key is
+     *                        pressed down. The delay duration must be between
+     *                        {@link #MIN_KEY_REPEAT_DELAY_MILLIS} and
+     *                        {@link #MAX_KEY_REPEAT_DELAY_MILLIS}
+     * <p>
+     * ‘Repeat keys’ is a feature which allows users to generate key repeats when a particular
+     * key on the physical keyboard is held down. This accessibility feature allows the user
+     * to configure the timeout before the key repeats begin as well as the delay
+     * between successive key repeats.
+     * </p>
+     *
+     * @hide
+     */
+    @TestApi
+    @FlaggedApi(FLAG_KEYBOARD_REPEAT_KEYS)
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setAccessibilityRepeatKeysDelay(@NonNull Context context,
+            int delayTimeMillis) {
+        if (delayTimeMillis < MIN_KEY_REPEAT_DELAY_MILLIS
+                || delayTimeMillis > MAX_KEY_REPEAT_DELAY_MILLIS) {
+            throw new IllegalArgumentException(
+                    "Provided repeat keys delay should be in range ("
+                            + MIN_KEY_REPEAT_DELAY_MILLIS + ","
+                            + MAX_KEY_REPEAT_DELAY_MILLIS + ")");
+        }
+        Settings.Secure.putIntForUser(context.getContentResolver(),
+                Settings.Secure.KEY_REPEAT_DELAY_MS, delayTimeMillis,
+                UserHandle.USER_CURRENT);
+    }
 }
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 83c4de3..077bd82 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -99,3 +99,10 @@
   description: "Refactor ModifierShortcutManager internal representation of shortcuts."
   bug: "358603902"
 }
+
+flag {
+  name: "keyboard_repeat_keys"
+  namespace: "input"
+  description: "Allow configurable timeout before key repeat and repeat delay rate for key repeats"
+  bug: "336585002"
+}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 3a58993..218b023 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -256,6 +256,10 @@
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     public int[] getContextHubHandles() {
+        if (Flags.removeOldContextHubApis()) {
+            return null;
+        }
+
         try {
             return mService.getContextHubHandles();
         } catch (RemoteException e) {
@@ -277,6 +281,10 @@
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     public ContextHubInfo getContextHubInfo(int hubHandle) {
+        if (Flags.removeOldContextHubApis()) {
+            return null;
+        }
+
         try {
             return mService.getContextHubInfo(hubHandle);
         } catch (RemoteException e) {
@@ -308,6 +316,10 @@
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     public int loadNanoApp(int hubHandle, @NonNull NanoApp app) {
+        if (Flags.removeOldContextHubApis()) {
+            return -1;
+        }
+
         try {
             return mService.loadNanoApp(hubHandle, app);
         } catch (RemoteException e) {
@@ -335,6 +347,10 @@
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     public int unloadNanoApp(int nanoAppHandle) {
+        if (Flags.removeOldContextHubApis()) {
+            return -1;
+        }
+
         try {
             return mService.unloadNanoApp(nanoAppHandle);
         } catch (RemoteException e) {
@@ -375,6 +391,10 @@
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @Nullable public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) {
+        if (Flags.removeOldContextHubApis()) {
+            return null;
+        }
+
         try {
             return mService.getNanoAppInstanceInfo(nanoAppHandle);
         } catch (RemoteException e) {
@@ -398,6 +418,10 @@
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     @NonNull public int[] findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter) {
+        if (Flags.removeOldContextHubApis()) {
+            return null;
+        }
+
         try {
             return mService.findNanoAppOnHub(hubHandle, filter);
         } catch (RemoteException e) {
@@ -433,6 +457,10 @@
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
     public int sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message) {
+        if (Flags.removeOldContextHubApis()) {
+            return -1;
+        }
+
         try {
             return mService.sendMessage(hubHandle, nanoAppHandle, message);
         } catch (RemoteException e) {
@@ -648,6 +676,10 @@
     @Deprecated
     @SuppressLint("RequiresPermission")
     public int registerCallback(@NonNull Callback callback) {
+        if (Flags.removeOldContextHubApis()) {
+            return -1;
+        }
+
         return registerCallback(callback, null);
     }
 
@@ -657,6 +689,10 @@
      */
     @Deprecated
     public int registerCallback(ICallback callback) {
+        if (Flags.removeOldContextHubApis()) {
+            return -1;
+        }
+
         if (mLocalCallback != null) {
             Log.w(TAG, "Max number of local callbacks reached!");
             return -1;
@@ -682,6 +718,10 @@
     @Deprecated
     @SuppressLint("RequiresPermission")
     public int registerCallback(Callback callback, Handler handler) {
+        if (Flags.removeOldContextHubApis()) {
+            return -1;
+        }
+
         synchronized(this) {
             if (mCallback != null) {
                 Log.w(TAG, "Max number of callbacks reached!");
@@ -1041,16 +1081,20 @@
     @SuppressLint("RequiresPermission")
     @Deprecated
     public int unregisterCallback(@NonNull Callback callback) {
-      synchronized(this) {
-          if (callback != mCallback) {
-              Log.w(TAG, "Cannot recognize callback!");
-              return -1;
-          }
+        if (Flags.removeOldContextHubApis()) {
+            return -1;
+        }
 
-          mCallback = null;
-          mCallbackHandler = null;
-      }
-      return 0;
+        synchronized (this) {
+            if (callback != mCallback) {
+                Log.w(TAG, "Cannot recognize callback!");
+                return -1;
+            }
+
+            mCallback = null;
+            mCallbackHandler = null;
+        }
+        return 0;
     }
 
     /**
@@ -1059,6 +1103,10 @@
      */
     @Deprecated
     public synchronized int unregisterCallback(ICallback callback) {
+        if (Flags.removeOldContextHubApis()) {
+            return -1;
+        }
+
         if (callback != mLocalCallback) {
             Log.w(TAG, "Cannot recognize local callback!");
             return -1;
diff --git a/core/java/android/hardware/radio/flags.aconfig b/core/java/android/hardware/radio/flags.aconfig
index c9ab62d..c99dc04 100644
--- a/core/java/android/hardware/radio/flags.aconfig
+++ b/core/java/android/hardware/radio/flags.aconfig
@@ -8,3 +8,11 @@
     description: "Feature flag for improved HD radio support with less vendor extensions"
     bug: "280300929"
 }
+
+flag {
+    name: "hd_radio_emergency_alert_system"
+    is_exported: true
+    namespace: "car_framework"
+    description: "Feature flag for HD radio emergency alert system support"
+    bug: "361348719"
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index bfff4db..9f3e3ad 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -29,6 +29,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -874,10 +875,9 @@
     /*****************************************************************************
      * A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound
      * patterns.
-     *
-     * @hide
      ****************************************************************************/
-    public static class GenericSoundModel extends SoundModel implements Parcelable {
+    @FlaggedApi(android.media.soundtrigger.Flags.FLAG_GENERIC_MODEL_API)
+    public static final class GenericSoundModel extends SoundModel implements Parcelable {
 
         public static final @android.annotation.NonNull Parcelable.Creator<GenericSoundModel> CREATOR
                 = new Parcelable.Creator<GenericSoundModel>() {
@@ -890,11 +890,26 @@
             }
         };
 
+        /**
+         * Constructor for {@link GenericSoundModel} with version.
+         *
+         * @param uuid Unique identifier for this sound model.
+         * @param vendorUuid Unique vendor identifier for this sound model.
+         * @param data Opaque data for this sound model.
+         * @param version Vendor-specific version number of this sound model.
+         */
         public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
                 @Nullable byte[] data, int version) {
             super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version);
         }
 
+        /**
+         * Constructor for {@link GenericSoundModel} without version. The version is set to -1.
+         *
+         * @param uuid Unique identifier for this sound model.
+         * @param vendorUuid Unique vendor identifier for this sound model.
+         * @param data Opaque data for this sound model.
+         */
         @UnsupportedAppUsage
         public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
                 @Nullable byte[] data) {
@@ -919,7 +934,7 @@
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeString(getUuid().toString());
             if (getVendorUuid() == null) {
                 dest.writeInt(-1);
diff --git a/core/java/android/os/ArtModuleServiceManager.java b/core/java/android/os/ArtModuleServiceManager.java
index e0b631d..995094b 100644
--- a/core/java/android/os/ArtModuleServiceManager.java
+++ b/core/java/android/os/ArtModuleServiceManager.java
@@ -37,10 +37,12 @@
     /** A class that exposes the method to obtain each system service. */
     public static final class ServiceRegisterer {
         @NonNull private final String mServiceName;
+        private final boolean mRetry;
 
         /** @hide */
-        public ServiceRegisterer(@NonNull String serviceName) {
+        public ServiceRegisterer(@NonNull String serviceName, boolean retry) {
             mServiceName = serviceName;
+            mRetry = retry;
         }
 
         /**
@@ -53,27 +55,47 @@
          */
         @Nullable
         public IBinder waitForService() {
-            return ServiceManager.waitForService(mServiceName);
+            if (mRetry) {
+                return ServiceManager.waitForService(mServiceName);
+            }
+            IBinder binder = ServiceManager.getService(mServiceName);
+            for (int remainingTimeMs = 5000; binder == null && remainingTimeMs > 0;
+                    remainingTimeMs -= 100) {
+                // There can be a race:
+                // 1. Client A invokes "ctl.start", which starts the service.
+                // 2. Client A gets a service handle from `ServiceManager.getService`.
+                // 3. Client B invokes "ctl.start", which does nothing because the service is
+                //    already running.
+                // 4. Client A drops the service handle. The service is notified that there is no
+                //    more client at that point, so it shuts down itself.
+                // 5. Client B cannot get a service handle from `ServiceManager.getService` because
+                //    the service is shut down.
+                // To address this problem, we invoke "ctl.start" repeatedly.
+                SystemProperties.set("ctl.start", mServiceName);
+                SystemClock.sleep(100);
+                binder = ServiceManager.getService(mServiceName);
+            }
+            return binder;
         }
     }
 
     /** Returns {@link ServiceRegisterer} for the "artd" service. */
     @NonNull
     public ServiceRegisterer getArtdServiceRegisterer() {
-        return new ServiceRegisterer("artd");
+        return new ServiceRegisterer("artd", true /* retry */);
     }
 
     /** Returns {@link ServiceRegisterer} for the "artd_pre_reboot" service. */
     @NonNull
     @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
     public ServiceRegisterer getArtdPreRebootServiceRegisterer() {
-        return new ServiceRegisterer("artd_pre_reboot");
+        return new ServiceRegisterer("artd_pre_reboot", false /* retry */);
     }
 
     /** Returns {@link ServiceRegisterer} for the "dexopt_chroot_setup" service. */
     @NonNull
     @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
     public ServiceRegisterer getDexoptChrootSetupServiceRegisterer() {
-        return new ServiceRegisterer("dexopt_chroot_setup");
+        return new ServiceRegisterer("dexopt_chroot_setup", true /* retry */);
     }
 }
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/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 1100731..c22f46c 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -646,6 +646,37 @@
     private native boolean unlinkToDeathNative(DeathRecipient recipient, int flags);
 
     /**
+     * This list is to hold strong reference to the frozen state callbacks. The callbacks are only
+     * weakly referenced by JNI so the strong references here are needed to keep the callbacks
+     * around until the proxy is GC'ed.
+     */
+    private List<IFrozenStateChangeCallback> mFrozenStateChangeCallbacks =
+            Collections.synchronizedList(new ArrayList<>());
+
+    /**
+     * See {@link IBinder#addFrozenStateChangeCallback(IFrozenStateChangeCallback)}
+     */
+    public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback)
+            throws RemoteException {
+        addFrozenStateChangeCallbackNative(callback);
+        mFrozenStateChangeCallbacks.add(callback);
+    }
+
+    /**
+     * See {@link IBinder#removeFrozenStateChangeCallback}
+     */
+    public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) {
+        mFrozenStateChangeCallbacks.remove(callback);
+        return removeFrozenStateChangeCallbackNative(callback);
+    }
+
+    private native void addFrozenStateChangeCallbackNative(IFrozenStateChangeCallback callback)
+            throws RemoteException;
+
+    private native boolean removeFrozenStateChangeCallbackNative(
+            IFrozenStateChangeCallback callback);
+
+    /**
      * Perform a dump on the remote object
      *
      * @param fd The raw file descriptor that the dump is being sent to.
@@ -730,6 +761,17 @@
         }
     }
 
+    private static void invokeFrozenStateChangeCallback(
+            IFrozenStateChangeCallback callback, IBinder binderProxy, int stateIndex) {
+        try {
+            callback.onFrozenStateChanged(binderProxy,
+                    IFrozenStateChangeCallback.State.values()[stateIndex]);
+        } catch (RuntimeException exc) {
+            Log.w("BinderNative", "Uncaught exception from frozen state change callback",
+                    exc);
+        }
+    }
+
     /**
      * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
      * native IBinder object, and a DeathRecipientList.
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 92b630f..80f39bf 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.
      */
@@ -798,6 +798,12 @@
     /**
      * Remove any pending posts of messages with code 'what' that are in the
      * message queue.
+     *
+     * Note that `Message#what` is 0 unless otherwise set.
+     * When calling `postMessage(Runnable)` or `postAtTime(Runnable, long)`,
+     * the `Runnable` is internally wrapped with a `Message` whose `what` is 0.
+     * Calling `removeMessages(0)` will remove all messages without a `what`,
+     * including posted `Runnable`s.
      */
     public final void removeMessages(int what) {
         mQueue.removeMessages(this, what, null);
@@ -889,7 +895,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/IBinder.java b/core/java/android/os/IBinder.java
index 50242ba..8185e8e 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -376,4 +376,53 @@
      * return value instead.
      */
     public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);
+
+    /** @hide */
+    interface IFrozenStateChangeCallback {
+        enum State {FROZEN, UNFROZEN};
+
+        /**
+         * Interface for receiving a callback when the process hosting an IBinder
+         * has changed its frozen state.
+         * @param who The IBinder whose hosting process has changed state.
+         * @param state The latest state.
+         */
+        void onFrozenStateChanged(@NonNull IBinder who, State state);
+    }
+
+    /**
+     * {@link addFrozenStateChangeCallback} provides a callback mechanism to notify about process
+     * frozen/unfrozen events. Upon registration and any subsequent state changes, the callback is
+     * invoked with the latest process frozen state.
+     *
+     * <p>If the listener process (the one using this API) is itself frozen, state change events
+     * might be combined into a single one with the latest frozen state. This single event would
+     * then be delivered when the listener process becomes unfrozen. Similarly, if an event happens
+     * before the previous event is consumed, they might be combined. This means the callback might
+     * not be called for every single state change, so don't rely on this API to count how many
+     * times the state has changed.</p>
+     *
+     * <p>The callback is automatically removed when all references to the binder proxy are
+     * dropped.</p>
+     *
+     * <p>You will only receive state change notifications for remote binders, as local binders by
+     * definition can't be frozen without you being frozen too.</p>
+     *
+     * <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support
+     * this feature.
+     * @hide
+     */
+    default void addFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback)
+            throws RemoteException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Unregister a {@link IFrozenStateChangeCallback}. The callback will no longer be invoked when
+     * the hosting process changes its frozen state.
+     * @hide
+     */
+    default boolean removeFrozenStateChangeCallback(@NonNull IFrozenStateChangeCallback callback) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index a1db9be..702fdc2 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -41,6 +41,9 @@
      * what this message is about. Each {@link Handler} has its own name-space
      * for message codes, so you do not need to worry about yours conflicting
      * with other handlers.
+     *
+     * If not specified, this value is 0.
+     * Use values other than 0 to indicate custom message codes.
      */
     public int what;
 
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 4cc057a..e80efd2 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -41,7 +41,6 @@
 import android.net.Uri;
 import android.os.MessageQueue.OnFileDescriptorEventListener;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
 import android.ravenwood.annotation.RavenwoodReplace;
 import android.ravenwood.annotation.RavenwoodThrow;
 import android.system.ErrnoException;
@@ -77,8 +76,6 @@
  * you to close it when done with it.
  */
 @RavenwoodKeepWholeClass
-@RavenwoodNativeSubstitutionClass(
-        "com.android.platform.test.ravenwood.nativesubstitution.ParcelFileDescriptor_host")
 public class ParcelFileDescriptor implements Parcelable, Closeable {
     private static final String TAG = "ParcelFileDescriptor";
 
@@ -206,11 +203,11 @@
         }
         mWrapped = null;
         mFd = fd;
-        setFdOwner(mFd);
+        IoUtils.setFdOwner(mFd, this);
 
         mCommFd = commChannel;
         if (mCommFd != null) {
-            setFdOwner(mCommFd);
+            IoUtils.setFdOwner(mCommFd, this);
         }
 
         mGuard.open("close");
@@ -298,7 +295,7 @@
     public static @NonNull ParcelFileDescriptor wrap(@NonNull ParcelFileDescriptor pfd,
             @NonNull Handler handler, @NonNull OnCloseListener listener) throws IOException {
         final FileDescriptor original = new FileDescriptor();
-        setFdInt(original, pfd.detachFd());
+        original.setInt$(pfd.detachFd());
         return fromFd(original, handler, listener);
     }
 
@@ -363,18 +360,10 @@
         }
     }
 
-    @RavenwoodReplace
     private static void closeInternal(FileDescriptor fd) {
         IoUtils.closeQuietly(fd);
     }
 
-    private static void closeInternal$ravenwood(FileDescriptor fd) {
-        try {
-            Os.close(fd);
-        } catch (ErrnoException ignored) {
-        }
-    }
-
     /**
      * Create a new ParcelFileDescriptor that is a dup of an existing
      * FileDescriptor.  This obeys standard POSIX semantics, where the
@@ -385,7 +374,7 @@
         try {
             final FileDescriptor fd = new FileDescriptor();
             int intfd = Os.fcntlInt(orig, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0);
-            setFdInt(fd, intfd);
+            fd.setInt$(intfd);
             return new ParcelFileDescriptor(fd);
         } catch (ErrnoException e) {
             throw e.rethrowAsIOException();
@@ -418,12 +407,12 @@
      */
     public static ParcelFileDescriptor fromFd(int fd) throws IOException {
         final FileDescriptor original = new FileDescriptor();
-        setFdInt(original, fd);
+        original.setInt$(fd);
 
         try {
             final FileDescriptor dup = new FileDescriptor();
             int intfd = Os.fcntlInt(original, (isAtLeastQ() ? F_DUPFD_CLOEXEC : F_DUPFD), 0);
-            setFdInt(dup, intfd);
+            dup.setInt$(intfd);
             return new ParcelFileDescriptor(dup);
         } catch (ErrnoException e) {
             throw e.rethrowAsIOException();
@@ -446,7 +435,7 @@
      */
     public static ParcelFileDescriptor adoptFd(int fd) {
         final FileDescriptor fdesc = new FileDescriptor();
-        setFdInt(fdesc, fd);
+        fdesc.setInt$(fd);
 
         return new ParcelFileDescriptor(fdesc);
     }
@@ -703,7 +692,7 @@
     @RavenwoodThrow(reason = "Os.readlink() and Os.stat()")
     public static File getFile(FileDescriptor fd) throws IOException {
         try {
-            final String path = Os.readlink("/proc/self/fd/" + getFdInt(fd));
+            final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
             if (OsConstants.S_ISREG(Os.stat(path).st_mode)
                     || OsConstants.S_ISCHR(Os.stat(path).st_mode)) {
                 return new File(path);
@@ -783,7 +772,7 @@
             if (mClosed) {
                 throw new IllegalStateException("Already closed");
             }
-            return getFdInt(mFd);
+            return mFd.getInt$();
         }
     }
 
@@ -805,7 +794,7 @@
             if (mClosed) {
                 throw new IllegalStateException("Already closed");
             }
-            int fd = acquireRawFd(mFd);
+            int fd = IoUtils.acquireRawFd(mFd);
             writeCommStatusAndClose(Status.DETACHED, null);
             mClosed = true;
             mGuard.close();
@@ -1265,38 +1254,6 @@
         }
     }
 
-    private static native void setFdInt$ravenwood(FileDescriptor fd, int fdInt);
-    private static native int getFdInt$ravenwood(FileDescriptor fd);
-
-    @RavenwoodReplace
-    private static void setFdInt(FileDescriptor fd, int fdInt) {
-        fd.setInt$(fdInt);
-    }
-
-    @RavenwoodReplace
-    private static int getFdInt(FileDescriptor fd) {
-        return fd.getInt$();
-    }
-
-    @RavenwoodReplace
-    private void setFdOwner(FileDescriptor fd) {
-        IoUtils.setFdOwner(fd, this);
-    }
-
-    private void setFdOwner$ravenwood(FileDescriptor fd) {
-        // FD owners currently unsupported under Ravenwood; ignored
-    }
-
-    @RavenwoodReplace
-    private int acquireRawFd(FileDescriptor fd) {
-        return IoUtils.acquireRawFd(fd);
-    }
-
-    private int acquireRawFd$ravenwood(FileDescriptor fd) {
-        // FD owners currently unsupported under Ravenwood; return FD directly
-        return getFdInt(fd);
-    }
-
     @RavenwoodReplace
     private static boolean isAtLeastQ() {
         return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index db06a6b..3b2041b 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -838,16 +838,11 @@
     /**
      * Returns true if the current process is a 64-bit runtime.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean is64Bit() {
         return VMRuntime.getRuntime().is64Bit();
     }
 
-    /** @hide */
-    public static final boolean is64Bit$ravenwood() {
-        return "amd64".equals(System.getProperty("os.arch"));
-    }
-
     private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
 
     /** @hide */
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..46ae9d8 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -379,6 +379,14 @@
         private int mReferenceCount;
 
         private MemoryRegistration(int size) {
+            // Round up to the nearest page size
+            final int PAGE_SIZE = OsConstants._SC_PAGE_SIZE;
+            if (PAGE_SIZE > 0) {
+                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/TransactionTooLargeException.java b/core/java/android/os/TransactionTooLargeException.java
index 79892e0..6b7cb33 100644
--- a/core/java/android/os/TransactionTooLargeException.java
+++ b/core/java/android/os/TransactionTooLargeException.java
@@ -47,7 +47,7 @@
  * If possible, try to break up big requests into smaller pieces.
  * </p><p>
  * If you are implementing a service, it may help to impose size or complexity
- * contraints on the queries that clients can perform.  For example, if the result set
+ * constraints on the queries that clients can perform.  For example, if the result set
  * could become large, then don't allow the client to request more than a few records
  * at a time.  Alternately, instead of returning all of the available data all at once,
  * return the essential information first and make the client ask for additional information
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/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index f02d4a9..64a2dbc 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -540,6 +540,17 @@
     /** @hide */
     public abstract void validate();
 
+
+    /**
+     * If supported, truncate the length of this vibration effect to the provided length and return
+     * the result. Will always return null for repeating effects.
+     *
+     * @return The desired effect, or {@code null} if truncation is not applicable.
+     * @hide
+     */
+    @Nullable
+    public abstract VibrationEffect cropToLengthOrNull(int length);
+
     /**
      * Gets the estimated duration of the vibration in milliseconds.
      *
@@ -866,6 +877,30 @@
             }
         }
 
+        /** @hide */
+        @Override
+        @Nullable
+        public VibrationEffect cropToLengthOrNull(int length) {
+            // drop repeating effects
+            if (mRepeatIndex >= 0) {
+                return null;
+            }
+
+            int segmentCount = mSegments.size();
+            if (segmentCount <= length) {
+                return this;
+            }
+
+            ArrayList truncated = new ArrayList(mSegments.subList(0, length));
+            Composed updated = new Composed(truncated, mRepeatIndex);
+            try {
+                updated.validate();
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+            return updated;
+        }
+
         @Override
         public long getDuration() {
             if (mRepeatIndex >= 0) {
@@ -1150,6 +1185,13 @@
                     "Vendor effect bundle must be non-empty");
         }
 
+        /** @hide */
+        @Override
+        @Nullable
+        public VibrationEffect cropToLengthOrNull(int length) {
+            return null;
+        }
+
         @Override
         public long getDuration() {
             return -1; // UNKNOWN
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..98904fe 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1974,7 +1974,7 @@
     /**
      * Activity Action: Show Notification Policy access settings.
      * <p>
-     * Users can grant and deny access to Notification Policy (DND / Priority Modes) configuration
+     * Users can grant and deny access to Notification Policy (DND / Modes) configuration
      * from here. Managed profiles cannot grant Notification Policy access.
      * See {@link android.app.NotificationManager#isNotificationPolicyAccessGranted()} for more
      * details.
@@ -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/security/OWNERS b/core/java/android/security/OWNERS
index 8bd6c85..c38ee08 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -3,6 +3,7 @@
 brambonne@google.com
 eranm@google.com
 jeffv@google.com
+tweek@google.com
 
 per-file *NetworkSecurityPolicy.java = file:net/OWNERS
 per-file Confirmation*.java = file:/keystore/OWNERS
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index 1f1a351..2b75493 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -2,6 +2,22 @@
 container: "system"
 
 flag {
+  name: "chooser_album_text"
+  is_exported: true
+  namespace: "intentresolver"
+  description: "Flag controlling the album text subtype hint for sharesheet"
+  bug: "323380224"
+}
+
+flag {
+  name: "enable_sharesheet_metadata_extra"
+  is_exported: true
+  namespace: "intentresolver"
+  description: "This flag enables sharesheet metadata to be displayed to users."
+  bug: "318942069"
+}
+
+flag {
   name: "chooser_payload_toggling"
   is_exported: true
   namespace: "intentresolver"
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 17d2790..711c414 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -28,7 +28,9 @@
 import android.util.Log;
 import android.view.WindowManager;
 
+import java.lang.ref.WeakReference;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 
 /**
@@ -49,46 +51,59 @@
      */
     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 DreamOverlayService mService;
+        private final WeakReference<DreamOverlayService> mService;
         private boolean mShowComplications;
+        private boolean mIsPreview;
         private ComponentName mDreamComponent;
         IDreamOverlayCallback mDreamOverlayCallback;
 
-        OverlayClient(DreamOverlayService service) {
+        OverlayClient(WeakReference<DreamOverlayService> service) {
             mService = service;
         }
 
+        private void applyToDream(Consumer<DreamOverlayService> consumer) {
+            final DreamOverlayService service = mService.get();
+
+            if (service != null) {
+                consumer.accept(service);
+            }
+        }
+
         @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;
-            mService.startDream(this, params);
+            applyToDream(dreamOverlayService -> dreamOverlayService.startDream(this, params));
         }
 
         @Override
         public void wakeUp() {
-            mService.wakeUp(this);
+            applyToDream(dreamOverlayService -> dreamOverlayService.wakeUp(this));
         }
 
         @Override
         public void endDream() {
-            mService.endDream(this);
+            applyToDream(dreamOverlayService -> dreamOverlayService.endDream(this));
         }
 
         @Override
         public void comeToFront() {
-            mService.comeToFront(this);
+            applyToDream(dreamOverlayService -> dreamOverlayService.comeToFront(this));
         }
 
         @Override
         public void onWakeRequested() {
             if (Flags.dreamWakeRedirect()) {
-                mService.onWakeRequested();
+                applyToDream(DreamOverlayService::onWakeRequested);
             }
         }
 
@@ -112,6 +127,10 @@
             return mShowComplications;
         }
 
+        private boolean isDreamInPreviewMode() {
+            return mIsPreview;
+        }
+
         private ComponentName getComponent() {
             return mDreamComponent;
         }
@@ -122,6 +141,10 @@
         mExecutor.execute(() -> {
             endDreamInternal(mCurrentClient);
             mCurrentClient = client;
+            if (Flags.dreamWakeRedirect() && mCurrentRedirectToWake != null) {
+                mCurrentClient.redirectWake(mCurrentRedirectToWake);
+            }
+
             onStartDream(params);
         });
     }
@@ -161,17 +184,24 @@
         });
     }
 
-    private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+    private static class DreamOverlay extends IDreamOverlay.Stub {
+        private final WeakReference<DreamOverlayService> mService;
+
+        DreamOverlay(DreamOverlayService service) {
+            mService = new WeakReference<>(service);
+        }
+
         @Override
         public void getClient(IDreamOverlayClientCallback callback) {
             try {
-                callback.onDreamOverlayClient(
-                        new OverlayClient(DreamOverlayService.this));
+                callback.onDreamOverlayClient(new OverlayClient(mService));
             } catch (RemoteException e) {
                 Log.e(TAG, "could not send client to callback", e);
             }
         }
-    };
+    }
+
+    private final IDreamOverlay mDreamOverlay = new DreamOverlay(this);
 
     public DreamOverlayService() {
     }
@@ -195,6 +225,12 @@
         }
     }
 
+    @Override
+    public void onDestroy() {
+        mCurrentClient = null;
+        super.onDestroy();
+    }
+
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
@@ -259,8 +295,10 @@
             return;
         }
 
+        mCurrentRedirectToWake = redirect;
+
         if (mCurrentClient == null) {
-            throw new IllegalStateException("redirected wake with no dream present");
+            return;
         }
 
         mCurrentClient.redirectWake(redirect);
@@ -272,7 +310,6 @@
      *
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_DREAM_WAKE_REDIRECT)
     public void onWakeRequested() {
     }
 
@@ -289,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..3d8d933 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)
@@ -1037,23 +1044,19 @@
                     rt.suppressedVisualEffects = safeInt(parser, DISALLOW_ATT_VISUAL_EFFECTS,
                             DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
                 } else if (MANUAL_TAG.equals(tag)) {
-                    ZenRule manualRule = readRuleXml(parser);
-                    if (manualRule != null) {
-                        rt.manualRule = manualRule;
-
-                        // Manual rule may be present prior to modes_ui if it were on, but in that
-                        // case it would not have a set policy, so make note of the need to set
-                        // it up later.
-                        readManualRule = true;
-                        if (rt.manualRule.zenPolicy == null) {
-                            readManualRuleWithoutPolicy = true;
-                        }
+                    rt.manualRule = readRuleXml(parser);
+                    // Manual rule may be present prior to modes_ui if it were on, but in that
+                    // case it would not have a set policy, so make note of the need to set
+                    // it up later.
+                    readManualRule = true;
+                    if (rt.manualRule.zenPolicy == null) {
+                        readManualRuleWithoutPolicy = true;
                     }
                 } else if (AUTOMATIC_TAG.equals(tag)
                         || (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
                     final String id = parser.getAttributeValue(null, RULE_ATT_ID);
-                    final ZenRule automaticRule = readRuleXml(parser);
-                    if (id != null && automaticRule != null) {
+                    if (id != null) {
+                        final ZenRule automaticRule = readRuleXml(parser);
                         automaticRule.id = id;
                         if (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag)) {
                             String deletedRuleKey = deletedRuleKey(automaticRule);
@@ -1170,16 +1173,13 @@
         out.endTag(null, ZEN_TAG);
     }
 
+    @NonNull
     public static ZenRule readRuleXml(TypedXmlPullParser parser) {
         final ZenRule rt = new ZenRule();
         rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true);
         rt.name = parser.getAttributeValue(null, RULE_ATT_NAME);
         final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN);
-        rt.zenMode = tryParseZenMode(zen, -1);
-        if (rt.zenMode == -1) {
-            Slog.w(TAG, "Bad zen mode in rule xml:" + zen);
-            return null;
-        }
+        rt.zenMode = tryParseZenMode(zen, ZEN_MODE_IMPORTANT_INTERRUPTIONS);
         rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
         rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
         rt.configurationActivity = safeComponentName(parser, RULE_ATT_CONFIG_ACTIVITY);
@@ -2607,7 +2607,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;
@@ -2932,7 +2932,7 @@
             }
         }
 
-        // TODO: b/333527800 - Rename to isActive()
+        // TODO: b/363193376 - Rename to isActive()
         public boolean isAutomaticActive() {
             if (Flags.modesApi() && Flags.modesUi()) {
                 if (!enabled || getPkg() == null) {
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 2669391..be0d7b3 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -393,6 +393,46 @@
     }
 
     /**
+     * Base Zen Policy used when {@link android.app.NotificationManager#setInterruptionFilter} is
+     * called with {@link android.app.NotificationManager#INTERRUPTION_FILTER_ALARMS} or an
+     * {@link android.app.AutomaticZenRule} specifies said filter.
+     *
+     * <p>Note that <em>visual effects</em> for filtered notifications are unset in this base
+     * policy, so should be merged on top of the default policy's visual effects (see
+     * {@link #overwrittenWith(ZenPolicy)}).
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static ZenPolicy getBasePolicyInterruptionFilterAlarms() {
+        return new ZenPolicy.Builder()
+                .disallowAllSounds()
+                .allowAlarms(true)
+                .allowMedia(true)
+                .allowPriorityChannels(false)
+                .build();
+    }
+
+    /**
+     * Base Zen Policy used when {@link android.app.NotificationManager#setInterruptionFilter} is
+     * called with {@link android.app.NotificationManager#INTERRUPTION_FILTER_NONE} or an
+     * {@link android.app.AutomaticZenRule} specifies said filter.
+     *
+     * <p>Note that <em>visual effects</em> for filtered notifications are unset in this base
+     * policy, so it should be merged on top of the device default policy's visual effects (see
+     * {@link #overwrittenWith(ZenPolicy)}).
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static ZenPolicy getBasePolicyInterruptionFilterNone() {
+        return new ZenPolicy.Builder()
+                .disallowAllSounds()
+                .allowPriorityChannels(false)
+                .build();
+    }
+
+    /**
      * Conversation type that can bypass DND.
      * @return {@link #CONVERSATION_SENDERS_UNSET}, {@link #CONVERSATION_SENDERS_ANYONE},
      * {@link #CONVERSATION_SENDERS_IMPORTANT}, {@link #CONVERSATION_SENDERS_NONE}.
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 1734223..ad457ce 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -25,6 +25,7 @@
 import static android.graphics.Matrix.MSKEW_Y;
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.flags.Flags.disableDrawWakeLock;
 
 import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
 import static com.android.window.flags.Flags.noDuplicateSurfaceDestroyedEvents;
@@ -51,6 +52,7 @@
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
@@ -208,6 +210,15 @@
     private boolean mIsWearOs;
 
     /**
+     * This change disables the {@code DRAW_WAKE_LOCK}, an internal wakelock acquired per-frame
+     * duration display DOZE. It was added to allow animation during AOD. This wakelock consumes
+     * battery severely if the animation is too heavy, so, it will be removed.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    private static final long DISABLE_DRAW_WAKE_LOCK_WALLPAPER = 361433696L;
+
+    /**
      * Wear products currently force a slight scaling transition to wallpapers
      * when the QSS is opened. However, on Wear 6 (SDK 35) and above, 1P watch faces
      * will be expected to either implement their own scaling, or to override this
@@ -362,6 +373,8 @@
         private SurfaceControl mScreenshotSurfaceControl;
         private Point mScreenshotSize = new Point();
 
+        private final boolean mDisableDrawWakeLock;
+
         final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
             {
                 mRequestedFormat = PixelFormat.RGBX_8888;
@@ -406,6 +419,9 @@
             }
 
             private void prepareToDraw() {
+                if (mDisableDrawWakeLock) {
+                    return;
+                }
                 if (mDisplayState == Display.STATE_DOZE) {
                     try {
                         mSession.pokeDrawLock(mWindow);
@@ -546,6 +562,8 @@
         public Engine(Supplier<Long> clockFunction, Handler handler) {
             mClockFunction = clockFunction;
             mHandler = handler;
+            mDisableDrawWakeLock = CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK_WALLPAPER)
+                    && disableDrawWakeLock();
         }
 
         /**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 40070c7..d33c95e 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -297,3 +297,10 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "typeface_redesign"
+  namespace: "text"
+  description: "Decouple variation settings, weight and style information from Typeface class"
+  bug: "361260253"
+}
diff --git a/core/java/android/util/Half.java b/core/java/android/util/Half.java
index fe536a6..22583ac 100644
--- a/core/java/android/util/Half.java
+++ b/core/java/android/util/Half.java
@@ -19,6 +19,7 @@
 import android.annotation.HalfFloat;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import libcore.util.FP16;
 
@@ -92,6 +93,7 @@
  * <p>This table shows that numbers higher than 1024 lose all fractional precision.</p>
  */
 @SuppressWarnings("SimplifiableIfStatement")
+@RavenwoodKeepWholeClass
 public final class Half extends Number implements Comparable<Half> {
     /**
      * The number of bits used to represent a half-precision float value.
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index feb80cc..ca5798a 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -112,6 +112,11 @@
         }
     }
 
+    // @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    public synchronized void clear() {
+        mLog.clear();
+    }
+
     public static class ReadOnlyLocalLog {
         private final LocalLog mLog;
         ReadOnlyLocalLog(LocalLog log) {
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index be1ec41..9845f9e 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -18,7 +18,6 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
@@ -226,16 +225,10 @@
         }
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     private Map.Entry<K, V> eldest() {
         return map.eldest();
     }
 
-    private Map.Entry<K, V> eldest$ravenwood() {
-        final Iterator<Map.Entry<K, V>> it = map.entrySet().iterator();
-        return it.hasNext() ? it.next() : null;
-    }
-
     /**
      * Removes the entry for {@code key} if it exists.
      *
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 15b0c13..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 */
@@ -2344,6 +2352,8 @@
          * SurfaceControl.DisplayMode
          * @hide
          */
+        @SuppressWarnings("UnflaggedApi") // For testing only
+        @TestApi
         public boolean isSynthetic() {
             return mIsSynthetic;
         }
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/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index c7e93c1..b801465 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -149,15 +149,17 @@
 
     private void setPreCommitProgress(float progress) {
         if (isHideAnimationInProgress()) return;
+        setInterpolatedProgress(BACK_GESTURE.getInterpolation(progress) * PEEK_FRACTION);
+    }
+
+    private void setInterpolatedProgress(float progress) {
         if (mWindowInsetsAnimationController != null) {
             float hiddenY = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
             float shownY = mWindowInsetsAnimationController.getShownStateInsets().bottom;
             float imeHeight = shownY - hiddenY;
-            float interpolatedProgress = BACK_GESTURE.getInterpolation(progress);
-            int newY = (int) (imeHeight - interpolatedProgress * (imeHeight * PEEK_FRACTION));
+            int newY = (int) (imeHeight - progress * imeHeight);
             if (mStartRootScrollY != 0) {
-                mViewRoot.setScrollY(
-                        (int) (mStartRootScrollY * (1 - interpolatedProgress * PEEK_FRACTION)));
+                mViewRoot.setScrollY((int) (mStartRootScrollY * (1 - progress)));
             }
             mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, newY), 1f,
                     progress);
@@ -171,21 +173,14 @@
             return;
         }
         mTriggerBack = triggerBack;
-        int currentBottomInset = mWindowInsetsAnimationController.getCurrentInsets().bottom;
-        int targetBottomInset;
-        if (triggerBack) {
-            targetBottomInset = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
-        } else {
-            targetBottomInset = mWindowInsetsAnimationController.getShownStateInsets().bottom;
-        }
-        mPostCommitAnimator = ValueAnimator.ofFloat(currentBottomInset, targetBottomInset);
+        float targetProgress = triggerBack ? 1f : 0f;
+        mPostCommitAnimator = ValueAnimator.ofFloat(
+                BACK_GESTURE.getInterpolation(mLastProgress) * PEEK_FRACTION, targetProgress);
         mPostCommitAnimator.setInterpolator(
                 triggerBack ? STANDARD_ACCELERATE : EMPHASIZED_DECELERATE);
         mPostCommitAnimator.addUpdateListener(animation -> {
-            int bottomInset = (int) ((float) animation.getAnimatedValue());
             if (mWindowInsetsAnimationController != null) {
-                mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, bottomInset),
-                        1f, animation.getAnimatedFraction());
+                setInterpolatedProgress((float) animation.getAnimatedValue());
             } else {
                 reset();
             }
@@ -213,14 +208,8 @@
             notifyHideIme();
             // requesting IME as invisible during post-commit
             mInsetsController.setRequestedVisibleTypes(0, ime());
-            // Changes the animation state. This also notifies RootView of changed insets, which
-            // causes it to reset its scrollY to 0f (animated) if it was panned
             mInsetsController.onAnimationStateChanged(ime(), /*running*/ true);
         }
-        if (mStartRootScrollY != 0 && !triggerBack) {
-            // This causes RootView to update its scroll back to the panned position
-            mInsetsController.getHost().notifyInsetsChanged();
-        }
     }
 
     private void notifyHideIme() {
@@ -282,6 +271,10 @@
         return mPostCommitAnimator != null && mTriggerBack;
     }
 
+    boolean isAnimationInProgress() {
+        return mIsPreCommitAnimationInProgress || mWindowInsetsAnimationController != null;
+    }
+
     /**
      * Dump information about this ImeBackAnimationController
      *
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 6343313..e90b1c0 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -70,7 +70,14 @@
                         "ImeInsetsSourceConsumer#onAnimationFinished",
                         mController.getHost().getInputMethodManager(), null /* icProto */);
             }
-            boolean insetsChanged = super.onAnimationStateChanged(running);
+            boolean insetsChanged = false;
+            if (Flags.predictiveBackIme() && !running && isShowRequested()
+                    && mAnimationState == ANIMATION_STATE_HIDE) {
+                // A user controlled hide animation may have ended in the shown state (e.g.
+                // cancelled predictive back animation) -> Insets need to be reset to shown.
+                insetsChanged |= applyLocalVisibilityOverride();
+            }
+            insetsChanged |= super.onAnimationStateChanged(running);
             if (running && !isShowRequested()
                     && mController.isPredictiveBackImeHideAnimInProgress()) {
                 // IME predictive back animation switched from pre-commit to post-commit.
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..8fdf91a 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,13 +1193,12 @@
 
         // 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,
-                pendingRequest.useInsetsAnimationThread, statsToken);
+        controlAnimationUnchecked(pendingRequest.types, pendingRequest.cancellationSignal,
+                pendingRequest.listener, null /* frame */, true /* fromIme */,
+                pendingRequest.mInsetsAnimationSpec,
+                pendingRequest.animationType, pendingRequest.layoutInsetsDuringAnimation,
+                pendingRequest.useInsetsAnimationThread, statsToken,
+                false /* fromPredictiveBack */);
     }
 
     @Override
@@ -1317,7 +1308,10 @@
             WindowInsetsAnimationControlListener listener,
             boolean fromIme, long durationMs, @Nullable Interpolator interpolator,
             @AnimationType int animationType, boolean fromPredictiveBack) {
-        if ((mState.calculateUncontrollableInsetsFromFrame(mFrame) & types) != 0) {
+        if ((mState.calculateUncontrollableInsetsFromFrame(mFrame) & types) != 0
+                || (fromPredictiveBack && ((mRequestedVisibleTypes & ime()) == 0))) {
+            // abort if insets are uncontrollable or if control request is from predictive back but
+            // there is already a hide anim in progress
             listener.onCancelled(null);
             return;
         }
@@ -1327,20 +1321,29 @@
                     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),
-                false /* useInsetsAnimationThread */, null);
+        controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, spec,
+                animationType, getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
+                false /* useInsetsAnimationThread */, null, fromPredictiveBack);
     }
 
     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) {
+            boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken,
+            boolean fromPredictiveBack) {
         final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 
         // Basically, we accept the requested visibilities from the upstream callers...
@@ -1349,8 +1352,8 @@
         // 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,
-                useInsetsAnimationThread, statsToken);
+                insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
+                useInsetsAnimationThread, statsToken, fromPredictiveBack);
 
         // We are finishing setting the requested visible types. Report them to the server
         // and/or the app.
@@ -1360,10 +1363,10 @@
     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) {
+            boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken,
+            boolean fromPredictiveBack) {
         if ((types & mTypesBeingCancelled) != 0) {
             final boolean monitoredAnimation =
                     animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
@@ -1418,8 +1421,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
@@ -1449,7 +1452,7 @@
             }
         } else {
             Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
-                    fromIme, types, controls, animationType, statsToken);
+                    fromIme, types, controls, animationType, statsToken, fromPredictiveBack);
             typesReady = typesReadyPair.first;
             boolean imeReady = typesReadyPair.second;
             if (DEBUG) {
@@ -1460,11 +1463,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 +1521,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) {
@@ -1587,7 +1588,7 @@
      */
     private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
             SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
-            @Nullable ImeTracker.Token statsToken) {
+            @Nullable ImeTracker.Token statsToken, boolean fromPredictiveBack) {
         ImeTracker.forLogging().onProgress(statsToken,
                 ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
 
@@ -1599,7 +1600,8 @@
                 continue;
             }
             boolean show = animationType == ANIMATION_TYPE_SHOW
-                    || animationType == ANIMATION_TYPE_USER;
+                    || (animationType == ANIMATION_TYPE_USER
+                            && (!fromPredictiveBack || !mHost.hasAnimationCallbacks()));
             boolean canRun = true;
             if (show) {
                 // Show request
@@ -1622,7 +1624,8 @@
                         break;
                 }
             } else {
-                consumer.requestHide(fromIme, statsToken);
+                consumer.requestHide(fromIme
+                        || (fromPredictiveBack && mHost.hasAnimationCallbacks()), statsToken);
             }
             if (!canRun) {
                 if (WARN) Log.w(TAG, String.format(
@@ -1677,9 +1680,10 @@
 
     private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
             @InsetsType int types, boolean fromPredictiveBack) {
-        if (fromPredictiveBack) {
-            // When insets are animated by predictive back, we want insets to be shown to prevent a
-            // jump cut from shown to hidden at the start of the predictive back animation
+        if (fromPredictiveBack && !mHost.hasAnimationCallbacks()) {
+            // When insets are animated by predictive back and the app does not have an animation
+            // callback, we want insets to be shown to prevent a jump cut from shown to hidden at
+            // the start of the predictive back animation
             return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
         }
         // Generally, we want to layout the opposite of the current state. This is to make animation
@@ -2023,10 +2027,11 @@
         // 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);
+                !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken,
+                false /* fromPredictiveBack */);
     }
 
     /**
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/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 72d2d3b..326e34b 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -128,7 +128,16 @@
  *             ev.getPointerId(p), ev.getX(p), ev.getY(p));
  *     }
  * }
- * </code></pre></p>
+ * </code></pre></p><p>
+ * Developers should keep in mind that it is especially important to consume all samples
+ * in a batched event when processing relative values that report changes since the last
+ * event or sample. Examples of such relative axes include {@link #AXIS_RELATIVE_X},
+ * {@link #AXIS_RELATIVE_Y}, and many of the axes prefixed with {@code AXIS_GESTURE_}.
+ * In these cases, developers should first consume all historical values using
+ * {@link #getHistoricalAxisValue(int, int)} and then consume the current values using
+ * {@link #getAxisValue(int)} like in the example above, as these relative values are
+ * not accumulated in a batched event.
+ * </p>
  *
  * <h3>Device Types</h3>
  * <p>
@@ -1117,6 +1126,9 @@
      * the location but this axis reports the difference which allows the app to see
      * how the mouse is moved.
      * </ul>
+     * </p><p>
+     * These values are relative to the state from the last sample, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical samples.
      * </p>
      *
      * @see #getAxisValue(int, int)
@@ -1130,6 +1142,9 @@
      * Axis constant: The movement of y position of a motion event.
      * <p>
      * This is similar to {@link #AXIS_RELATIVE_X} but for y-axis.
+     * </p><p>
+     * These values are relative to the state from the last sample, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical samples.
      * </p>
      *
      * @see #getAxisValue(int, int)
@@ -1324,8 +1339,8 @@
      * swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of
      * -0.1.
      * </ul>
-     * These values are relative to the state from the last event, not accumulated, so developers
-     * should make sure to process this axis value for all batched historical events.
+     * These values are relative to the state from the last sample, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical samples.
      * <p>
      * This axis is only set on the first pointer in a motion event.
      */
@@ -1345,8 +1360,8 @@
      * <li>For a touch pad, reports the distance that should be scrolled in the X axis as a result
      * of the user's two-finger scroll gesture, in display pixels.
      * </ul>
-     * These values are relative to the state from the last event, not accumulated, so developers
-     * should make sure to process this axis value for all batched historical events.
+     * These values are relative to the state from the last sample, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical samples.
      * <p>
      * This axis is only set on the first pointer in a motion event.
      */
@@ -1367,8 +1382,8 @@
      * making a pinch gesture, as a proportion of the previous distance. For example, if the fingers
      * were 50 units apart and are now 52 units apart, the scale factor would be 1.04.
      * </ul>
-     * These values are relative to the state from the last event, not accumulated, so developers
-     * should make sure to process this axis value for all batched historical events.
+     * These values are relative to the state from the last sample, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical samples.
      * <p>
      * This axis is only set on the first pointer in a motion event.
      */
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 1535145..815fd1c 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -556,12 +556,9 @@
                                 + "is a different type from the others. All frames should be the "
                                 + "same type.");
                     }
-                    if (drawableFrame.getIntrinsicWidth() != width ||
-                        drawableFrame.getIntrinsicHeight() != height) {
-                        throw new IllegalArgumentException("The bitmap size of " + i + "-th frame "
-                                + "is different. All frames should have the exact same size and "
-                                + "share the same hotspot.");
-                    }
+                    // TODO(b/361232935): Check when bitmap size of the ith frame is different
+                    // drawableFrame.getIntrinsicWidth() != width ||
+                    // drawableFrame.getIntrinsicHeight() != height
                     if (isVectorAnimation) {
                         drawableFrame = getBitmapDrawableFromVectorDrawable(resources,
                                 (VectorDrawable) drawableFrame, pointerScale);
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/View.java b/core/java/android/view/View.java
index e81f32e..7b4ea41 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -141,7 +141,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.os.Vibrator;
 import android.service.credentials.CredentialProviderService;
 import android.sysprop.DisplayProperties;
 import android.text.InputType;
@@ -27378,6 +27377,29 @@
     }
 
     /**
+     * Modifiers the input matrix such that it maps root view's coordinates to view-local
+     * coordinates.
+     *
+     * @param matrix input matrix to modify
+     * @hide
+     */
+    public void transformMatrixRootToLocal(@NonNull Matrix matrix) {
+        final ViewParent parent = mParent;
+        if (parent instanceof final View vp) {
+            vp.transformMatrixRootToLocal(matrix);
+            matrix.postTranslate(vp.mScrollX, vp.mScrollY);
+        }
+        // This method is different from transformMatrixToLocal that it doesn't perform any
+        // transformation for ViewRootImpl
+
+        matrix.postTranslate(-mLeft, -mTop);
+
+        if (!hasIdentityMatrix()) {
+            matrix.postConcat(getInverseMatrix());
+        }
+    }
+
+    /**
      * @hide
      */
     @ViewDebug.ExportedProperty(category = "layout", indexMapping = {
@@ -34156,7 +34178,8 @@
      * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, REQUESTED_FRAME_RATE_CATEGORY_HIGH.
      * Keep in mind that the preferred frame rate affects the frame rate for the next frame,
      * so use this method carefully. It's important to note that the preference is valid as
-     * long as the View is invalidated.
+     * long as the View is invalidated. Please also be aware that the requested frame rate
+     * will not propagate to child views when this API is used on a ViewGroup.
      *
      * @param frameRate the preferred frame rate of the view.
      */
@@ -34175,6 +34198,8 @@
      * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW,
      * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, and REQUESTED_FRAME_RATE_CATEGORY_HIGH.
      * Note that the frame rate value is valid as long as the View is invalidated.
+     * Please also be aware that the requested frame rate will not propagate to
+     * child views when this API is used on a ViewGroup.
      *
      * @return REQUESTED_FRAME_RATE_CATEGORY_DEFAULT by default,
      * or value passed to {@link #setRequestedFrameRate(float)}.
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index fb2b8b9..63bf392 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -373,6 +373,7 @@
     private final int mMaximumDrawingCacheSize;
     private final int mOverscrollDistance;
     private final int mOverflingDistance;
+    private final boolean mViewTouchScreenHapticScrollFeedbackEnabled;
     @UnsupportedAppUsage
     private final boolean mFadingMarqueeEnabled;
     private final long mGlobalActionsKeyTimeout;
@@ -437,6 +438,7 @@
         mSmartSelectionInitializedTimeout = SMART_SELECTION_INITIALIZED_TIMEOUT_IN_MILLISECOND;
         mSmartSelectionInitializingTimeout = SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND;
         mPreferKeepClearForFocusEnabled = false;
+        mViewTouchScreenHapticScrollFeedbackEnabled = false;
     }
 
     /**
@@ -588,6 +590,12 @@
         mViewBasedRotaryEncoderScrollHapticsEnabledConfig =
                 res.getBoolean(
                         com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
+        mViewTouchScreenHapticScrollFeedbackEnabled =
+                Flags.enableTouchScrollFeedback()
+                        ? res.getBoolean(
+                        com.android.internal.R.bool
+                                .config_viewTouchScreenHapticScrollFeedbackEnabled)
+                        : false;
     }
 
     /**
@@ -1285,6 +1293,10 @@
             return mRotaryEncoderHapticScrollFeedbackEnabled;
         }
 
+        if ((source & InputDevice.SOURCE_TOUCHSCREEN) != 0) {
+            return mViewTouchScreenHapticScrollFeedbackEnabled;
+        }
+
         return false;
     }
 
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..f021bdf 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();
@@ -6101,6 +6094,12 @@
     }
 
     boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
+        if (mImeBackAnimationController.isAnimationInProgress()) {
+            // IME predictive back animation is currently in progress which means that scrollY is
+            // currently controlled by ImeBackAnimationController.
+            return false;
+        }
+
         final Rect ci = mAttachInfo.mContentInsets;
         final Rect vi = mAttachInfo.mVisibleInsets;
         int scrollY = 0;
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/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index a4cea33..a87e5c8 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1051,6 +1051,52 @@
     }
 
     /**
+     * Registers callback for when user initialization has completed.
+     * Does nothing if the same callback is already registered.
+     *
+     * @param callback The callback to be registered
+     * @hide
+     */
+    public void registerUserInitializationCompleteCallback(
+            @NonNull IUserInitializationCompleteCallback callback) {
+        IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.registerUserInitializationCompleteCallback(callback);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while registering userInitializationCompleteCallback. ", re);
+        }
+    }
+
+    /**
+     * Unregisters callback for when user initialization has completed.
+     *
+     * @param callback The callback to be unregistered
+     * @hide
+     */
+    public void unregisterUserInitializationCompleteCallback(
+            @NonNull IUserInitializationCompleteCallback callback) {
+        IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.unregisterUserInitializationCompleteCallback(callback);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG,
+                    "Error while unregistering userInitializationCompleteCallback. ", re);
+        }
+    }
+
+    /**
      * Whether the current accessibility request comes from an
      * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
      * property set to true.
@@ -2049,9 +2095,7 @@
      * {@link android.view.Display#DEFAULT_DISPLAY}, is or lower than
      * {@link android.view.Display#INVALID_DISPLAY}, or is already being proxy-ed.
      *
-     * @throws SecurityException if the app does not hold the
-     * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission or the
-     * {@link Manifest.permission#CREATE_VIRTUAL_DEVICE} permission.
+     * @throws SecurityException if the app does not hold the required permissions.
      *
      * @hide
      */
@@ -2079,9 +2123,7 @@
      *
      * @return {@code true} if the proxy is successfully unregistered.
      *
-     * @throws SecurityException if the app does not hold the
-     * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission or the
-     * {@link Manifest.permission#CREATE_VIRTUAL_DEVICE} permission.
+     * @throws SecurityException if the app does not hold the required permissions.
      *
      * @hide
      */
@@ -2134,8 +2176,8 @@
         try {
             return service.startFlashNotificationSequence(context.getOpPackageName(),
                     reason, mBinder);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while start flash notification sequence", re);
+        } catch (RemoteException | SecurityException e) {
+            Log.e(LOG_TAG, "Error while start flash notification sequence", e);
             return false;
         }
     }
@@ -2164,8 +2206,8 @@
 
         try {
             return service.stopFlashNotificationSequence(context.getOpPackageName());
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while stop flash notification sequence", re);
+        } catch (RemoteException | SecurityException e) {
+            Log.e(LOG_TAG, "Error while stop flash notification sequence", e);
             return false;
         }
     }
@@ -2192,8 +2234,8 @@
         try {
             return service.startFlashNotificationEvent(context.getOpPackageName(),
                     reason, reasonPkg);
-        } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error while start flash notification event", re);
+        } catch (RemoteException | SecurityException e) {
+            Log.e(LOG_TAG, "Error while start flash notification event", e);
             return false;
         }
     }
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/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 72a1fe4..2de3ce8 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -29,6 +29,7 @@
 import android.view.accessibility.IAccessibilityManagerClient;
 import android.view.accessibility.AccessibilityWindowAttributes;
 import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IUserInitializationCompleteCallback;
 import android.view.InputEvent;
 import android.view.IWindow;
 import android.view.MagnificationSpec;
@@ -156,13 +157,13 @@
     @EnforcePermission("INJECT_EVENTS")
     void injectInputEventToInputFilter(in InputEvent event);
 
-    @RequiresNoPermission
+    @EnforcePermission("MANAGE_ACCESSIBILITY")
     boolean startFlashNotificationSequence(String opPkg, int reason, IBinder token);
 
-    @RequiresNoPermission
+    @EnforcePermission("MANAGE_ACCESSIBILITY")
     boolean stopFlashNotificationSequence(String opPkg);
 
-    @RequiresNoPermission
+    @EnforcePermission("MANAGE_ACCESSIBILITY")
     boolean startFlashNotificationEvent(String opPkg, int reason, String reasonPkg);
 
     @RequiresNoPermission
@@ -192,4 +193,10 @@
 
     @EnforcePermission("MANAGE_ACCESSIBILITY")
     Bundle getA11yFeatureToTileMap(int userId);
+
+    @RequiresNoPermission
+    void registerUserInitializationCompleteCallback(IUserInitializationCompleteCallback callback);
+
+    @RequiresNoPermission
+    void unregisterUserInitializationCompleteCallback(IUserInitializationCompleteCallback callback);
 }
diff --git a/core/java/android/view/accessibility/IUserInitializationCompleteCallback.aidl b/core/java/android/view/accessibility/IUserInitializationCompleteCallback.aidl
new file mode 100644
index 0000000..fe6c8e2
--- /dev/null
+++ b/core/java/android/view/accessibility/IUserInitializationCompleteCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.accessibility;
+
+/**
+ * A callback for when a new user finishes initializing
+ * NOTE: Must remain a oneway interface, as it is called from system_server while holding a lock.
+ * oneway allows it to return immediately and not hold the lock for longer than is necessary.
+ * @hide
+ */
+
+oneway interface IUserInitializationCompleteCallback {
+
+    /**
+     * Called when a user initialization completes.
+     *
+     * @param userId the id of the initialized user
+     */
+    @RequiresNoPermission
+    void onUserInitializationComplete(int userId);
+}
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index 338037f..e9c85684 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -14,4 +14,11 @@
     name: "use_view_based_rotary_encoder_scroll_haptics"
     description: "If enabled, the rotary encoder scroll haptic implementation in the View class will be used, and the HapticScrollFeedbackProvider logic for rotary encoder haptic will be muted."
     bug: "299587011"
-}
\ No newline at end of file
+}
+
+flag {
+    namespace: "toolkit"
+    name: "enable_touch_scroll_feedback"
+    description: "Enables touchscreen haptic scroll feedback"
+    bug: "331830899"
+}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 2f515fe..3a008aa 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -323,14 +323,14 @@
     static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
             @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
             int lastClickToolType, @Nullable ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+            @SoftInputShowHideReason int reason, boolean async) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return false;
         }
         try {
             return service.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
-                    resultReceiver, reason);
+                    resultReceiver, reason, async);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -339,14 +339,15 @@
     @AnyThread
     static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
             @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
-            @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason,
+            boolean async) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return false;
         }
         try {
             return service.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
-                    reason);
+                    reason, async);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -407,7 +408,7 @@
             @Nullable IRemoteInputConnection remoteInputConnection,
             @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             int unverifiedTargetSdkVersion, @UserIdInt int userId,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean useAsyncShowHideMethod) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return -1;
@@ -416,7 +417,7 @@
             service.startInputOrWindowGainedFocusAsync(startInputReason, client, windowToken,
                     startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
                     remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
-                    imeDispatcher, advanceAngGetStartInputSequenceNumber());
+                    imeDispatcher, advanceAngGetStartInputSequenceNumber(), useAsyncShowHideMethod);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index b281015..2f649c2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -56,6 +56,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
 import android.app.PropertyInvalidatedCache;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -441,6 +442,36 @@
     public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // This is a bug id.
 
     /**
+     * Use async method for {@link InputMethodManager#showSoftInput(View, int, ResultReceiver)},
+     * {@link InputMethodManager#showSoftInput(View, int)} and
+     * {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int, ResultReceiver)},
+     * {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)} for apps targeting V+.
+     * <p>
+     * Apps can incorrectly rely on {@link InputMethodManager#showSoftInput(View, int)} and
+     * {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)} method return type
+     * to interpret result of a request rather than relying on {@link ResultReceiver}. The return
+     * type of the method was never documented to have accurate info of visibility but few apps
+     * incorrectly rely on it.
+     * <p>
+     * Starting Android V, we use async calls into system_server which returns {@code true} if
+     * method call was made but return type doesn't guarantee execution.
+     * Apps targeting older versions will fallback to existing behavior of calling synchronous
+     * methods which had undocumented result in return type.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    private static final long USE_ASYNC_SHOW_HIDE_METHOD = 352594277L; // This is a bug id.
+
+    /**
+     * Version-gating is guarded by bug-fix flag.
+     */
+    private static final boolean ASYNC_SHOW_HIDE_METHOD_ENABLED =
+            !Flags.compatchangeForZerojankproxy()
+                || CompatChanges.isChangeEnabled(USE_ASYNC_SHOW_HIDE_METHOD);
+
+    /**
      * If {@code true}, avoid calling the
      * {@link com.android.server.inputmethod.InputMethodManagerService InputMethodManagerService}
      * by skipping the call to {@link IInputMethodManager#startInputOrWindowGainedFocus}
@@ -1365,10 +1396,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 {
@@ -2246,6 +2277,8 @@
      *             {@link View#isFocused view focus}, and its containing window has
      *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
      *             returns {@code false}.
+     * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note:
+     * this does not return result of the request. For result use {@param resultReceiver} instead.
      */
     public boolean showSoftInput(View view, @ShowFlags int flags) {
         // Re-dispatch if there is a context mismatch.
@@ -2315,6 +2348,8 @@
      * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
      * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
      * {@link #RESULT_HIDDEN}.
+     * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note:
+     * this does not return result of the request. For result use {@param resultReceiver} instead.
      */
     public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) {
         return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT);
@@ -2383,7 +2418,8 @@
                         flags,
                         mCurRootView.getLastClickToolType(),
                         resultReceiver,
-                        reason);
+                        reason,
+                        ASYNC_SHOW_HIDE_METHOD_ENABLED);
             }
         }
     }
@@ -2426,7 +2462,8 @@
                     flags,
                     mCurRootView.getLastClickToolType(),
                     resultReceiver,
-                    reason);
+                    reason,
+                    ASYNC_SHOW_HIDE_METHOD_ENABLED);
         }
     }
 
@@ -2459,6 +2496,9 @@
      *
      * @param windowToken The token of the window that is making the request,
      * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+     * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note:
+     * this does not return result of the request. For result use {@link ResultReceiver} in
+     * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)} instead.
      */
     public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags) {
         return hideSoftInputFromWindow(windowToken, flags, null);
@@ -2487,6 +2527,8 @@
      * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
      * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
      * {@link #RESULT_HIDDEN}.
+     * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note:
+     * this does not return result of the request. For result use {@param resultReceiver} instead.
      */
     public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
             ResultReceiver resultReceiver) {
@@ -2530,7 +2572,7 @@
                 return true;
             } else {
                 return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken,
-                        statsToken, flags, resultReceiver, reason);
+                        statsToken, flags, resultReceiver, reason, ASYNC_SHOW_HIDE_METHOD_ENABLED);
             }
         }
     }
@@ -2573,7 +2615,7 @@
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
 
             return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, view.getWindowToken(),
-                    statsToken, flags, null, reason);
+                    statsToken, flags, null, reason, ASYNC_SHOW_HIDE_METHOD_ENABLED);
         }
     }
 
@@ -3350,7 +3392,7 @@
                         servedInputConnection == null ? null
                                 : servedInputConnection.asIRemoteAccessibilityInputConnection(),
                         view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
-                        mImeDispatcher);
+                        mImeDispatcher, ASYNC_SHOW_HIDE_METHOD_ENABLED);
             } else {
                 res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
                         startInputReason, mClient, windowGainingFocus, startInputFlags,
@@ -3653,7 +3695,8 @@
                     statsToken,
                     HIDE_NOT_ALWAYS,
                     null,
-                    reason);
+                    reason,
+                    true /*async */);
         }
     }
 
@@ -3745,7 +3788,7 @@
 
             IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
                     0 /* flags */, null /* resultReceiver */,
-                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, true /* async */);
         }
     }
 
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index e294ee2..bae8aff 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -136,3 +136,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "compatchange_for_zerojankproxy"
+  namespace: "input_method"
+  description: "Version-gate the sync/async nature of IMM#show/hideSoftInput() when using zeroJankProxy."
+  bug: "352594277"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
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/webkit/WebView.java b/core/java/android/webkit/WebView.java
index f336b5d..ffe8c80 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -3088,11 +3088,11 @@
         }
 
         if (Flags.updateServiceIpcWrapper()) {
-            WebViewUpdateManager manager = WebViewUpdateManager.getInstance();
-            if (manager == null) {
+            if (WebViewFactory.isWebViewSupported()) {
+                return WebViewUpdateManager.getInstance().getCurrentWebViewPackage();
+            } else {
                 return null;
             }
-            return manager.getCurrentWebViewPackage();
         } else {
             IWebViewUpdateService service = WebViewFactory.getUpdateService();
             if (service == null) {
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index c53a0e1..e10a398 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -208,7 +208,7 @@
         public MissingWebViewPackageException(Exception e) { super(e); }
     }
 
-    private static boolean isWebViewSupported() {
+    static boolean isWebViewSupported() {
         // No lock; this is a benign race as Boolean's state is final and the PackageManager call
         // will always return the same value.
         if (sWebViewSupported == null) {
@@ -318,7 +318,7 @@
         String libraryFileName;
         try {
             PackageInfo packageInfo = packageManager.getPackageInfo(packageName,
-                    PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
+                    PackageManager.GET_META_DATA);
             libraryFileName = getWebViewLibrary(packageInfo.applicationInfo);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(LOGTAG, "Couldn't find package " + packageName);
@@ -479,7 +479,6 @@
                 newPackageInfo = pm.getPackageInfo(
                     response.packageInfo.packageName,
                     PackageManager.GET_SHARED_LIBRARY_FILES
-                    | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
                     // Make sure that we fetch the current provider even if its not
                     // installed for the current user
                     | PackageManager.MATCH_UNINSTALLED_PACKAGES
diff --git a/core/java/android/webkit/WebViewUpdateManager.java b/core/java/android/webkit/WebViewUpdateManager.java
index dd48df9..0eb71001 100644
--- a/core/java/android/webkit/WebViewUpdateManager.java
+++ b/core/java/android/webkit/WebViewUpdateManager.java
@@ -19,12 +19,14 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.os.RemoteException;
 
 /** @hide */
@@ -43,8 +45,11 @@
      *
      * This exists for the benefit of callsites without a {@link Context}; prefer
      * {@link Context#getSystemService(Class)} otherwise.
+     *
+     * This can only be used on devices with {@link PackageManager#FEATURE_WEBVIEW}.
      */
     @SuppressLint("ManagerLookup") // service opts in to getSystemServiceWithNoContext()
+    @RequiresFeature(PackageManager.FEATURE_WEBVIEW)
     public static @Nullable WebViewUpdateManager getInstance() {
         return (WebViewUpdateManager) SystemServiceRegistry.getSystemServiceWithNoContext(
                 Context.WEBVIEW_UPDATE_SERVICE);
diff --git a/core/java/android/webkit/WebViewUpdateService.java b/core/java/android/webkit/WebViewUpdateService.java
index 6f53dde..01af182 100644
--- a/core/java/android/webkit/WebViewUpdateService.java
+++ b/core/java/android/webkit/WebViewUpdateService.java
@@ -34,11 +34,11 @@
      */
     public static WebViewProviderInfo[] getAllWebViewPackages() {
         if (Flags.updateServiceIpcWrapper()) {
-            WebViewUpdateManager manager = WebViewUpdateManager.getInstance();
-            if (manager == null) {
+            if (WebViewFactory.isWebViewSupported()) {
+                return WebViewUpdateManager.getInstance().getAllWebViewPackages();
+            } else {
                 return new WebViewProviderInfo[0];
             }
-            return manager.getAllWebViewPackages();
         } else {
             IWebViewUpdateService service = getUpdateService();
             if (service == null) {
@@ -57,11 +57,11 @@
      */
     public static WebViewProviderInfo[] getValidWebViewPackages() {
         if (Flags.updateServiceIpcWrapper()) {
-            WebViewUpdateManager manager = WebViewUpdateManager.getInstance();
-            if (manager == null) {
+            if (WebViewFactory.isWebViewSupported()) {
+                return WebViewUpdateManager.getInstance().getValidWebViewPackages();
+            } else {
                 return new WebViewProviderInfo[0];
             }
-            return manager.getValidWebViewPackages();
         } else {
             IWebViewUpdateService service = getUpdateService();
             if (service == null) {
@@ -80,11 +80,11 @@
      */
     public static String getCurrentWebViewPackageName() {
         if (Flags.updateServiceIpcWrapper()) {
-            WebViewUpdateManager manager = WebViewUpdateManager.getInstance();
-            if (manager == null) {
+            if (WebViewFactory.isWebViewSupported()) {
+                return WebViewUpdateManager.getInstance().getCurrentWebViewPackageName();
+            } else {
                 return null;
             }
-            return manager.getCurrentWebViewPackageName();
         } else {
             IWebViewUpdateService service = getUpdateService();
             if (service == null) {
diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig
index defe61e..b21a490 100644
--- a/core/java/android/webkit/flags.aconfig
+++ b/core/java/android/webkit/flags.aconfig
@@ -9,3 +9,12 @@
     bug: "319292658"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "mainline_apis"
+    is_exported: true
+    namespace: "webview"
+    description: "New APIs required by WebViewBootstrap mainline module"
+    bug: "310653407"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index ab6b512..3c854ea 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.view.flags.Flags.enableTouchScrollFeedback;
+import static android.view.flags.Flags.scrollFeedbackApi;
 import static android.view.flags.Flags.viewVelocityApi;
 
 import android.annotation.ColorInt;
@@ -82,7 +84,6 @@
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.ContentCaptureSession;
-import android.view.flags.Flags;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
@@ -3703,7 +3704,6 @@
                 // If it's non-null, we're already in a scroll.
                 mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
             }
-
             if (y != mLastY) {
                 // We may be here after stopping a fling and continuing to scroll.
                 // If so, we haven't disallowed intercepting touch events yet.
@@ -3735,8 +3735,15 @@
                 boolean atEdge = false;
                 if (incrementalDeltaY != 0) {
                     atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
-                }
 
+                    // TODO: b/360198915 - Add unit testing for using ScrollFeedbackProvider
+                    if (enableTouchScrollFeedback()) {
+                        initHapticScrollFeedbackProviderIfNotExists();
+                        mHapticScrollFeedbackProvider.onScrollProgress(
+                                vtev.getDeviceId(), vtev.getSource(), MotionEvent.AXIS_Y,
+                                incrementalDeltaY);
+                    }
+                }
                 // Check to see if we have bumped into the scroll limit
                 motionView = this.getChildAt(motionIndex);
                 if (motionView != null) {
@@ -3745,7 +3752,6 @@
                     final int motionViewRealTop = motionView.getTop();
                     if (atEdge) {
                         // Apply overscroll
-
                         int overscroll = -incrementalDeltaY -
                                 (motionViewRealTop - motionViewPrevTop);
                         if (dispatchNestedScroll(0, overscroll - incrementalDeltaY, 0, overscroll,
@@ -3772,6 +3778,15 @@
                                     mDirection = 0; // Reset when entering overscroll.
                                     mTouchMode = TOUCH_MODE_OVERSCROLL;
                                 }
+
+                                if (enableTouchScrollFeedback()) {
+                                    initHapticScrollFeedbackProviderIfNotExists();
+                                    mHapticScrollFeedbackProvider.onScrollLimit(
+                                            vtev.getDeviceId(), vtev.getSource(),
+                                            MotionEvent.AXIS_Y,
+                                            /* isStart= */ incrementalDeltaY > 0);
+                                }
+
                                 if (incrementalDeltaY > 0) {
                                     mEdgeGlowTop.onPullDistance((float) -overscroll / getHeight(),
                                             (float) x / getWidth());
@@ -3981,7 +3996,6 @@
         if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
             return true;
         }
-
         initVelocityTrackerIfNotExists();
         final MotionEvent vtev = MotionEvent.obtain(ev);
 
@@ -4520,7 +4534,7 @@
                     final int overscrollMode = getOverScrollMode();
 
                     if (!trackMotionScroll(delta, delta)) {
-                        if (Flags.scrollFeedbackApi()) {
+                        if (scrollFeedbackApi()) {
                             initHapticScrollFeedbackProviderIfNotExists();
                             mHapticScrollFeedbackProvider.onScrollProgress(
                                     event.getDeviceId(), event.getSource(), axis, delta);
@@ -4536,7 +4550,7 @@
                         float overscroll = (delta - (motionViewRealTop - motionViewPrevTop))
                                 / ((float) getHeight());
                         boolean hitTopLimit = delta > 0;
-                        if (Flags.scrollFeedbackApi()) {
+                        if (scrollFeedbackApi()) {
                             initHapticScrollFeedbackProviderIfNotExists();
                             mHapticScrollFeedbackProvider.onScrollLimit(
                                     event.getDeviceId(), event.getSource(), axis,
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/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 1922327..eb35817 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,7 +83,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.system.Os;
 import android.text.TextUtils;
@@ -6247,18 +6246,6 @@
 
     private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
             @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
-        try {
-            Trace.beginSection(rv.hasDrawInstructions()
-                    ? "RemoteViews#inflateViewWithDrawInstructions"
-                    : "RemoteViews#inflateView");
-            return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources);
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent,
-            @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
         // RemoteViews may be built by an application installed in another
         // user. So build a context that loads resources from that user but
         // still returns the current users userId so settings like data / time formats
@@ -6397,7 +6384,7 @@
 
         private View mResult;
         private ViewTree mTree;
-        private List<Action> mActions;
+        private Action[] mActions;
         private Exception mError;
 
         private AsyncApplyTask(
@@ -6424,20 +6411,11 @@
 
                 if (mRV.mActions != null) {
                     int count = mRV.mActions.size();
-                    mActions = new ArrayList<>(count);
-                    try {
-                        Trace.beginSection(hasDrawInstructions()
-                                ? "RemoteViews#initActionAsyncWithDrawInstructions"
-                                : "RemoteViews#initActionAsync");
-                        for (Action action : mRV.mActions) {
-                            if (isCancelled()) {
-                                break;
-                            }
-                            // TODO: check if isCancelled in nested views.
-                            mActions.add(action.initActionAsync(mTree, mParent, mApplyParams));
-                        }
-                    } finally {
-                        Trace.endSection();
+                    mActions = new Action[count];
+                    for (int i = 0; i < count && !isCancelled(); i++) {
+                        // TODO: check if isCancelled in nested views.
+                        mActions[i] = mRV.mActions.get(i)
+                                .initActionAsync(mTree, mParent, mApplyParams);
                     }
                 } else {
                     mActions = null;
@@ -6459,7 +6437,14 @@
 
                 try {
                     if (mActions != null) {
-                        mRV.performApply(viewTree.mRoot, mParent, mApplyParams, mActions);
+
+                        ActionApplyParams applyParams = mApplyParams.clone();
+                        if (applyParams.handler == null) {
+                            applyParams.handler = DEFAULT_INTERACTION_HANDLER;
+                        }
+                        for (Action a : mActions) {
+                            a.apply(viewTree.mRoot, mParent, applyParams);
+                        }
                     }
                     // If the parent of the view is has is a root, resolve the recycling.
                     if (mTopLevel && mResult instanceof ViewGroup) {
@@ -6635,11 +6620,6 @@
     }
 
     private void performApply(View v, ViewGroup parent, ActionApplyParams params) {
-        performApply(v, parent, params, mActions);
-    }
-
-    private void performApply(
-            View v, ViewGroup parent, ActionApplyParams params, List<Action> actions) {
         params = params.clone();
         if (params.handler == null) {
             params.handler = DEFAULT_INTERACTION_HANDLER;
@@ -6650,15 +6630,8 @@
         }
         if (mActions != null) {
             final int count = mActions.size();
-            try {
-                Trace.beginSection(hasDrawInstructions()
-                        ? "RemoteViews#applyActionsWithDrawInstructions"
-                        : "RemoteViews#applyActions");
-                for (int i = 0; i < count; i++) {
-                    mActions.get(i).apply(v, parent, params);
-                }
-            } finally {
-                Trace.endSection();
+            for (int i = 0; i < count; i++) {
+                mActions.get(i).apply(v, parent, params);
             }
         }
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 72b268b..a4b28ad 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);
         }
 
@@ -14375,7 +14375,7 @@
 
         Matrix matrix = mTempMatrix;
         matrix.reset();
-        transformMatrixToLocal(matrix);
+        transformMatrixRootToLocal(matrix);
         editorBounds.set(rect);
         // When the view has transformations like scaleX/scaleY computing the global visible
         // rectangle will already apply the transformations. The getLocalVisibleRect only offsets
@@ -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/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 58b5757..b8a11cf0 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -47,6 +47,19 @@
     void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
 
     /**
+     * Registers remote animations per transition type for the organizer. It will override the
+     * animations if the transition only contains windows that belong to the organized
+     * TaskFragments in the given Task.
+     */
+    void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
+        in RemoteAnimationDefinition definition);
+
+    /**
+     * Unregisters remote animations per transition type for the organizer.
+     */
+    void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
+
+    /**
      * Saves the state in the system, where the state can be restored if the process of
      * the TaskFragmentOrganizer is restarted.
      */
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 205f1de..9a7bce0 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;
@@ -152,9 +151,11 @@
         @VisibleForTesting
         public void setFrames(Rect frame, Rect systemBarInsets) {
             mFrame.set(frame);
-            mSystemBarInsets.set(systemBarInsets);
             mSizeMismatch = (mFrame.width() != mSnapshotW || mFrame.height() != mSnapshotH);
-            mSystemBarBackgroundPainter.setInsets(systemBarInsets);
+            if (!Flags.drawSnapshotAspectRatioMatch() && systemBarInsets != null) {
+                mSystemBarInsets.set(systemBarInsets);
+                mSystemBarBackgroundPainter.setInsets(systemBarInsets);
+            }
         }
 
         private void drawSnapshot(boolean releaseAfterDraw) {
@@ -185,7 +186,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 +193,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())
@@ -396,9 +396,12 @@
         final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
         final ActivityManager.TaskDescription taskDescription =
                 getOrCreateTaskDescription(runningTaskInfo);
-        drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags,
-                attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes);
-        final Rect systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState);
+        Rect systemBarInsets = null;
+        if (!Flags.drawSnapshotAspectRatioMatch()) {
+            drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags,
+                    attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes);
+            systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState);
+        }
         drawSurface.setFrames(windowBounds, systemBarInsets);
         drawSurface.drawSnapshot(releaseAfterDraw);
     }
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 027d323..4cc0d8a 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -34,6 +34,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
 
 import com.android.window.flags.Flags;
@@ -225,6 +226,34 @@
     }
 
     /**
+     * Registers remote animations per transition type for the organizer. It will override the
+     * animations if the transition only contains windows that belong to the organized
+     * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
+     * @hide
+     */
+    @CallSuper
+    public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
+        try {
+            getController().registerRemoteAnimations(mInterface, definition);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters remote animations per transition type for the organizer.
+     * @hide
+     */
+    @CallSuper
+    public void unregisterRemoteAnimations() {
+        try {
+            getController().unregisterRemoteAnimations(mInterface);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Saves the state in the system, where the state can be restored if the process of
      * the TaskFragmentOrganizer is restarted.
      *
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index 1555416..efd74c3 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -41,6 +41,8 @@
 
     private final int mDisplayId;
 
+    private final int mTaskId;
+
     private final boolean mVisible;
 
     private final boolean mHasDirectActivity;
@@ -49,9 +51,11 @@
 
     /** @hide */
     public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
-            boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) {
+            int taskId, boolean visible, boolean hasDirectActivity,
+            @Nullable SurfaceControl decorSurface) {
         mConfiguration.setTo(configuration);
         mDisplayId = displayId;
+        mTaskId = taskId;
         mVisible = visible;
         mHasDirectActivity = hasDirectActivity;
         mDecorSurface = decorSurface;
@@ -61,6 +65,7 @@
     public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
         mConfiguration.setTo(info.getConfiguration());
         mDisplayId = info.mDisplayId;
+        mTaskId = info.mTaskId;
         mVisible = info.mVisible;
         mHasDirectActivity = info.mHasDirectActivity;
         mDecorSurface = info.mDecorSurface;
@@ -87,6 +92,15 @@
     }
 
     /**
+     * The id of the parent Task.
+     *
+     * @hide
+     */
+    public int getTaskId() {
+        return mTaskId;
+    }
+
+    /**
      * Whether the parent Task is visible or not
      *
      * @hide
@@ -120,7 +134,8 @@
             return false;
         }
         return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
-                && mVisible == that.mVisible && mHasDirectActivity == that.mHasDirectActivity
+                && mTaskId == that.mTaskId && mVisible == that.mVisible
+                && mHasDirectActivity == that.mHasDirectActivity
                 && mDecorSurface == that.mDecorSurface;
     }
 
@@ -140,6 +155,7 @@
         return TaskFragmentParentInfo.class.getSimpleName() + ":{"
                 + "config=" + mConfiguration
                 + ", displayId=" + mDisplayId
+                + ", taskId=" + mTaskId
                 + ", visible=" + mVisible
                 + ", hasDirectActivity=" + mHasDirectActivity
                 + ", decorSurface=" + mDecorSurface
@@ -163,6 +179,7 @@
         final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj;
         return mConfiguration.equals(that.mConfiguration)
                 && mDisplayId == that.mDisplayId
+                && mTaskId == that.mTaskId
                 && mVisible == that.mVisible
                 && mHasDirectActivity == that.mHasDirectActivity
                 && mDecorSurface == that.mDecorSurface;
@@ -172,6 +189,7 @@
     public int hashCode() {
         int result = mConfiguration.hashCode();
         result = 31 * result + mDisplayId;
+        result = 31 * result + mTaskId;
         result = 31 * result + (mVisible ? 1 : 0);
         result = 31 * result + (mHasDirectActivity ? 1 : 0);
         result = 31 * result + Objects.hashCode(mDecorSurface);
@@ -183,6 +201,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         mConfiguration.writeToParcel(dest, flags);
         dest.writeInt(mDisplayId);
+        dest.writeInt(mTaskId);
         dest.writeBoolean(mVisible);
         dest.writeBoolean(mHasDirectActivity);
         dest.writeTypedObject(mDecorSurface, flags);
@@ -191,6 +210,7 @@
     private TaskFragmentParentInfo(Parcel in) {
         mConfiguration.readFromParcel(in);
         mDisplayId = in.readInt();
+        mTaskId = in.readInt();
         mVisible = in.readBoolean();
         mHasDirectActivity = in.readBoolean();
         mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR);
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..cab6d8e 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,20 @@
     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
+    }
+}
+
+flag {
+    name: "enable_desktop_windowing_persistence"
+    namespace: "lse_desktop_experience"
+    description: "Persists the desktop windowing session across reboots."
+    bug: "350456942"
+}
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 6ce9725..cd31850 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -71,3 +71,11 @@
     bug: "339720406"
 }
 
+flag {
+    name: "bal_reduce_grace_period"
+    namespace: "responsible_apis"
+    description: "Changes to reduce or ideally remove the grace period exemption."
+    bug: "362575865"
+}
+
+
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/TEST_MAPPING b/core/java/com/android/internal/accessibility/TEST_MAPPING
index 1c67399..b2b3041 100644
--- a/core/java/com/android/internal/accessibility/TEST_MAPPING
+++ b/core/java/com/android/internal/accessibility/TEST_MAPPING
@@ -2,6 +2,9 @@
   "imports": [
     {
       "path": "frameworks/base/services/accessibility/TEST_MAPPING"
+    },
+    {
+      "path": "frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/TEST_MAPPING"
     }
   ]
 }
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/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 9f5ed65..21fbf9d 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -134,7 +134,8 @@
      * Prints data on dumpsys.
      */
     public void dump(PrintWriter pw) {
-        pw.println("BrightnessSynchronizer");
+        pw.println("BrightnessSynchronizer:");
+        pw.println("-----------------------");
         pw.println("  mLatestIntBrightness=" + mLatestIntBrightness);
         pw.println("  mLatestFloatBrightness=" + mLatestFloatBrightness);
         pw.println("  mCurrentUpdate=" + mCurrentUpdate);
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/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 7bfb800..1204ef3 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -218,8 +218,25 @@
      */
     public static final int CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE = 117;
 
+    /**
+     * Track attempting to snap resize a desktop window via button or drag.
+     *
+     * <p>CUJ has 3 different tags:
+     * <ul>
+     *     <li>snap resizing resizable apps via maximize menu button: maximize_menu_resizable </li>
+     *     <li>snap resizing resizable via drag: drag_resizable </li>
+     *     <li>snap resizing non-resizable via drag: drag_non_resizable </li>
+     * </ul>
+     *
+     * <p>For non-resizable apps, the desktop window won't actually be resized, instead will return
+     * to its pre-dragged position. Attempting to snap resize a non-resizable app via the
+     * maximize menu will just result in no change, and a toast explaining the app can't be resized.
+     *
+     */
+    public static final int CUJ_DESKTOP_MODE_SNAP_RESIZE = 118;
+
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
-    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE;
+    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_SNAP_RESIZE;
 
     /** @hide */
     @IntDef({
@@ -328,7 +345,8 @@
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
             CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
             CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE,
-            CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
+            CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
+            CUJ_DESKTOP_MODE_SNAP_RESIZE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
@@ -448,6 +466,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_SNAP_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_SNAP_RESIZE;
     }
 
     private Cuj() {
@@ -678,6 +697,8 @@
                 return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE";
             case CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE:
                 return "DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE";
+            case CUJ_DESKTOP_MODE_SNAP_RESIZE:
+                return "DESKTOP_MODE_SNAP_RESIZE";
         }
         return "UNKNOWN";
     }
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..b6b8bc5
--- /dev/null
+++ b/core/java/com/android/internal/jank/flags.aconfig
@@ -0,0 +1,10 @@
+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"
+  is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index 1c6c6a7..656d4c7 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -211,6 +211,7 @@
                         if (mInstance != 0) {
                             native_replySimple(mInstance, unique, FUSE_OK);
                         }
+                        mCallbackMap.remove(checkInode(inode));
                         mBytesMap.stopUsing(inode);
                         recycleLocked(args);
                     }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b9cc457..2acda8a 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -631,21 +631,20 @@
      */
     private static Runnable forkSystemServer(String abiList, String socketName,
             ZygoteServer zygoteServer) {
-        long capabilities = posixCapabilitiesAsBits(
-                OsConstants.CAP_IPC_LOCK,
-                OsConstants.CAP_KILL,
-                OsConstants.CAP_NET_ADMIN,
-                OsConstants.CAP_NET_BIND_SERVICE,
-                OsConstants.CAP_NET_BROADCAST,
-                OsConstants.CAP_NET_RAW,
-                OsConstants.CAP_SYS_MODULE,
-                OsConstants.CAP_SYS_NICE,
-                OsConstants.CAP_SYS_PTRACE,
-                OsConstants.CAP_SYS_TIME,
-                OsConstants.CAP_SYS_TTY_CONFIG,
-                OsConstants.CAP_WAKE_ALARM,
-                OsConstants.CAP_BLOCK_SUSPEND
-        );
+        long capabilities =
+                (1L << OsConstants.CAP_IPC_LOCK) |
+                (1L << OsConstants.CAP_KILL) |
+                (1L << OsConstants.CAP_NET_ADMIN) |
+                (1L << OsConstants.CAP_NET_BIND_SERVICE) |
+                (1L << OsConstants.CAP_NET_BROADCAST) |
+                (1L << OsConstants.CAP_NET_RAW) |
+                (1L << OsConstants.CAP_SYS_MODULE) |
+                (1L << OsConstants.CAP_SYS_NICE) |
+                (1L << OsConstants.CAP_SYS_PTRACE) |
+                (1L << OsConstants.CAP_SYS_TIME) |
+                (1L << OsConstants.CAP_SYS_TTY_CONFIG) |
+                (1L << OsConstants.CAP_WAKE_ALARM) |
+                (1L << OsConstants.CAP_BLOCK_SUSPEND);
         /* Containers run without some capabilities, so drop any caps that are not available. */
         StructCapUserHeader header = new StructCapUserHeader(
                 OsConstants._LINUX_CAPABILITY_VERSION_3, 0);
@@ -742,20 +741,6 @@
     }
 
     /**
-     * Gets the bit array representation of the provided list of POSIX capabilities.
-     */
-    private static long posixCapabilitiesAsBits(int... capabilities) {
-        long result = 0;
-        for (int capability : capabilities) {
-            if ((capability < 0) || (capability > OsConstants.CAP_LAST_CAP)) {
-                throw new IllegalArgumentException(String.valueOf(capability));
-            }
-            result |= (1L << capability);
-        }
-        return result;
-    }
-
-    /**
      * This is the entry point for a Zygote process.  It creates the Zygote server, loads resources,
      * and handles other tasks related to preparing the process for forking into applications.
      *
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 4828393..58818f3 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -63,6 +63,9 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.graphics.drawable.LayerDrawable;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
@@ -114,6 +117,7 @@
 import com.android.window.flags.Flags;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /** @hide */
@@ -1348,8 +1352,15 @@
                     mCrossWindowBlurEnabled = enabled;
                     updateBackgroundBlurRadius();
                 };
+                // The executor to receive callback {@link mCrossWindowBlurEnabledListener}. It
+                // should be the executor for this {@link DecorView}'s ui thread (not necessarily
+                // the main thread).
+                final Executor executor = Looper.myLooper() == Looper.getMainLooper()
+                        ? getContext().getMainExecutor()
+                        : new HandlerExecutor(new Handler(Looper.myLooper()));
                 getContext().getSystemService(WindowManager.class)
-                        .addCrossWindowBlurEnabledListener(mCrossWindowBlurEnabledListener);
+                        .addCrossWindowBlurEnabledListener(
+                                executor, mCrossWindowBlurEnabledListener);
                 getViewTreeObserver().addOnPreDrawListener(mBackgroundBlurOnPreDrawListener);
             } else {
                 updateBackgroundBlurRadius();
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 0d0207f..e0529b3 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2513,9 +2513,16 @@
         }
 
         mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
+
+        // For floating windows that are *allowed* to fill the screen (like Wear) content
+        // should still be wrapped if they're not explicitly requested as fullscreen.
+        final boolean isFloatingAndFullscreen = mIsFloating
+                && mAllowFloatingWindowsFillScreen
+                && a.getBoolean(R.styleable.Window_windowFullscreen, false);
+
         int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                 & (~getForcedWindowFlags());
-        if (mIsFloating && !mAllowFloatingWindowsFillScreen) {
+        if (mIsFloating && !isFloatingAndFullscreen) {
             setLayout(WRAP_CONTENT, WRAP_CONTENT);
             setFlags(0, flagsToUpdate);
         } else {
@@ -3326,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/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 2feb3d5..8771cde 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -29,6 +29,7 @@
 import static com.android.internal.protolog.ProtoLogMessage.SINT64_PARAMS;
 import static com.android.internal.protolog.ProtoLogMessage.STR_PARAMS;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.ShellCommand;
 import android.os.SystemClock;
@@ -49,6 +50,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
@@ -419,6 +421,12 @@
         return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
     }
 
+    @Override
+    @NonNull
+    public List<IProtoLogGroup> getRegisteredGroups() {
+        return mLogGroups.values().stream().toList();
+    }
+
     public void registerGroups(IProtoLogGroup... protoLogGroups) {
         for (IProtoLogGroup group : protoLogGroups) {
             mLogGroups.put(group.name(), group);
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index e8d5195..34e0418 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.protolog.ProtoLog.REQUIRE_PROTOLOGTOOL;
 
+import android.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -26,6 +27,9 @@
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogLevel;
 
+import java.util.Collections;
+import java.util.List;
+
 /**
  * Class only create and used to server temporarily for when there is source code pre-processing by
  * the ProtoLog tool, when the tracing to Perfetto flag is off, and the static REQUIRE_PROTOLOGTOOL
@@ -36,6 +40,8 @@
  */
 @Deprecated
 public class LogcatOnlyProtoLogImpl implements IProtoLog {
+    private static final String LOG_TAG = LogcatOnlyProtoLogImpl.class.getName();
+
     @Override
     public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
             Object[] args) {
@@ -44,19 +50,21 @@
 
     @Override
     public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object[] args) {
-        if (REQUIRE_PROTOLOGTOOL) {
-            throw new RuntimeException(
-                    "REQUIRE_PROTOLOGTOOL not set to false before the first log call.");
+        if (REQUIRE_PROTOLOGTOOL && group.isLogToProto()) {
+            Log.w(LOG_TAG, "ProtoLog message not processed. Failed to log it to proto. "
+                    + "Logging it below to logcat instead.");
         }
 
-        String formattedString = TextUtils.formatSimple(messageString, args);
-        switch (logLevel) {
-            case VERBOSE -> Log.v(group.getTag(), formattedString);
-            case INFO -> Log.i(group.getTag(), formattedString);
-            case DEBUG -> Log.d(group.getTag(), formattedString);
-            case WARN -> Log.w(group.getTag(), formattedString);
-            case ERROR -> Log.e(group.getTag(), formattedString);
-            case WTF -> Log.wtf(group.getTag(), formattedString);
+        if (group.isLogToLogcat() || group.isLogToProto()) {
+            String formattedString = TextUtils.formatSimple(messageString, args);
+            switch (logLevel) {
+                case VERBOSE -> Log.v(group.getTag(), formattedString);
+                case INFO -> Log.i(group.getTag(), formattedString);
+                case DEBUG -> Log.d(group.getTag(), formattedString);
+                case WARN -> Log.w(group.getTag(), formattedString);
+                case ERROR -> Log.e(group.getTag(), formattedString);
+                case WTF -> Log.wtf(group.getTag(), formattedString);
+            }
         }
     }
 
@@ -79,4 +87,10 @@
     public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
         return true;
     }
+
+    @Override
+    @NonNull
+    public List<IProtoLogGroup> getRegisteredGroups() {
+        return Collections.emptyList();
+    }
 }
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 49ed55d..4264358 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;
         }
     }
 
@@ -306,6 +308,12 @@
                 || group.isLogToLogcat();
     }
 
+    @Override
+    @NonNull
+    public List<IProtoLogGroup> getRegisteredGroups() {
+        return mLogGroups.values().stream().toList();
+    }
+
     private void registerGroupsLocally(@NonNull IProtoLogGroup[] protoLogGroups) {
         final var groupsLoggingToLogcat = new ArrayList<String>();
         for (IProtoLogGroup protoLogGroup : protoLogGroups) {
@@ -922,37 +930,47 @@
     }
 
     private static class Message {
+        @Nullable
         private final Long mMessageHash;
+        @Nullable
         private final Integer mMessageMask;
+        @Nullable
         private final String mMessageString;
 
-        private Message(Long messageHash, int messageMask) {
+        private Message(long messageHash, int messageMask) {
             this.mMessageHash = messageHash;
             this.mMessageMask = messageMask;
             this.mMessageString = null;
         }
 
-        private Message(String messageString) {
+        private Message(@NonNull String messageString) {
             this.mMessageHash = null;
             final List<Integer> argTypes = LogDataType.parseFormatString(messageString);
             this.mMessageMask = LogDataType.logDataTypesToBitMask(argTypes);
             this.mMessageString = messageString;
         }
 
-        private int getMessageMask() {
+        @Nullable
+        private Integer getMessageMask() {
             return mMessageMask;
         }
 
+        @Nullable
         private String getMessage() {
             return mMessageString;
         }
 
+        @Nullable
         private String getMessage(@NonNull ProtoLogViewerConfigReader viewerConfigReader) {
             if (mMessageString != null) {
                 return mMessageString;
             }
 
-            return viewerConfigReader.getViewerString(mMessageHash);
+            if (mMessageHash != null) {
+                return viewerConfigReader.getViewerString(mMessageHash);
+            }
+
+            throw new RuntimeException("Both mMessageString and mMessageHash should never be null");
         }
     }
 }
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index f9b9894..bf77db7 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -20,6 +20,9 @@
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogLevel;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+
 /**
  * ProtoLog API - exposes static logging methods. Usage of this API is similar
  * to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
@@ -49,6 +52,8 @@
 
     private static IProtoLog sProtoLogInstance;
 
+    private static final Object sInitLock = new Object();
+
     /**
      * Initialize ProtoLog in this process.
      * <p>
@@ -58,11 +63,22 @@
      * @param groups The ProtoLog groups that will be used in the process.
      */
     public static void init(IProtoLogGroup... groups) {
+        // These tracing instances are only used when we cannot or do not preprocess the source
+        // files to extract out the log strings. Otherwise, the trace calls are replaced with calls
+        // directly to the generated tracing implementations.
         if (android.tracing.Flags.perfettoProtologTracing()) {
-            sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+            synchronized (sInitLock) {
+                if (sProtoLogInstance != null) {
+                    // The ProtoLog instance has already been initialized in this process
+                    final var alreadyRegisteredGroups = sProtoLogInstance.getRegisteredGroups();
+                    final var allGroups = new ArrayList<>(alreadyRegisteredGroups);
+                    allGroups.addAll(Arrays.stream(groups).toList());
+                    groups = allGroups.toArray(new IProtoLogGroup[0]);
+                }
+
+                sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+            }
         } else {
-            // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
-            // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
             sProtoLogInstance = new LogcatOnlyProtoLogImpl();
         }
     }
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 93%
rename from core/java/com/android/internal/protolog/ProtoLogService.java
rename to core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
index 2333a06..eeac139 100644
--- a/core/java/com/android/internal/protolog/ProtoLogService.java
+++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java
@@ -23,6 +23,7 @@
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG;
@@ -70,9 +71,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 +115,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.
@@ -210,8 +211,7 @@
          *                             want to write to the trace buffer.
          * @throws FileNotFoundException if the viewerConfigFilePath is invalid.
          */
-        void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath)
-                throws FileNotFoundException;
+        void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath);
     }
 
     @Override
@@ -351,11 +351,7 @@
 
     private void onTracingInstanceFlush() {
         for (String fileName : mConfigFileCounts.keySet()) {
-            try {
-                mViewerConfigFileTracer.trace(mDataSource, fileName);
-            } catch (FileNotFoundException e) {
-                throw new RuntimeException(e);
-            }
+            mViewerConfigFileTracer.trace(mDataSource, fileName);
         }
     }
 
@@ -364,10 +360,16 @@
     }
 
     private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource,
-            @NonNull String viewerConfigFilePath) throws FileNotFoundException {
-        final var pis = new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
-
+            @NonNull String viewerConfigFilePath) {
         dataSource.trace(ctx -> {
+            final ProtoInputStream pis;
+            try {
+                pis = new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+            } catch (FileNotFoundException e) {
+                throw new RuntimeException(
+                        "Failed to load viewer config file " + viewerConfigFilePath, e);
+            }
+
             try {
                 final ProtoOutputStream os = ctx.newTracePacket();
 
@@ -396,11 +398,7 @@
             mConfigFileCounts.put(configFile, newCount);
             boolean lastProcessWithViewerConfig = newCount == 0;
             if (lastProcessWithViewerConfig) {
-                try {
-                    mViewerConfigFileTracer.trace(mDataSource, configFile);
-                } catch (FileNotFoundException e) {
-                    throw new RuntimeException(e);
-                }
+                mViewerConfigFileTracer.trace(mDataSource, configFile);
             }
         }
     }
@@ -446,6 +444,7 @@
                 case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE));
                 case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL));
                 case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID));
+                case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION));
                 default ->
                     throw new RuntimeException(
                             "Unexpected field id " + pis.getFieldNumber());
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 6dc6585..837622f 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;
@@ -36,6 +37,7 @@
 import android.util.proto.ProtoInputStream;
 import android.util.proto.WireTypeMismatchException;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.LogLevel;
 
 import java.io.IOException;
@@ -43,11 +45,11 @@
 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,
         ProtoLogDataSource.IncrementalState> {
+    private static final String DATASOURCE_NAME = "android.protolog";
 
     private final Instance.TracingInstanceStartCallback mOnStart;
     private final Runnable mOnFlush;
@@ -55,7 +57,13 @@
 
     public ProtoLogDataSource(Instance.TracingInstanceStartCallback onStart, Runnable onFlush,
             Instance.TracingInstanceStopCallback onStop) {
-        super("android.protolog");
+        this(onStart, onFlush, onStop, DATASOURCE_NAME);
+    }
+
+    @VisibleForTesting
+    public ProtoLogDataSource(Instance.TracingInstanceStartCallback onStart, Runnable onFlush,
+            Instance.TracingInstanceStopCallback onStop, String dataSourceName) {
+        super(dataSourceName);
         this.mOnStart = onStart;
         this.mOnFlush = onFlush;
         this.mOnStop = onStop;
@@ -190,73 +198,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 +254,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/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 38ca0d8..3b24f27 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -3,7 +3,6 @@
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
-
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
@@ -11,7 +10,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.proto.ProtoInputStream;
 
@@ -38,6 +36,7 @@
      * Returns message format string for its hash or null if unavailable
      * or the viewer config is not loaded into memory.
      */
+    @Nullable
     public synchronized String getViewerString(long messageHash) {
         return mLogMessageMap.get(messageHash);
     }
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/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index d5c2ac1..f06f08a 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.protolog.common;
 
+import java.util.List;
+
 /**
  * Interface for ProtoLog implementations.
  */
@@ -68,4 +70,9 @@
      * @return If we need to log this group and level to either ProtoLog or Logcat.
      */
     boolean isEnabled(IProtoLogGroup group, LogLevel level);
+
+    /**
+     * @return an immutable list of the registered ProtoLog groups in this ProtoLog instance.
+     */
+    List<IProtoLogGroup> getRegisteredGroups();
 }
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index ee3a3c2..319efe0 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -60,7 +60,9 @@
     public static void ensureRavenwoodInitialized() {
     }
 
-    private static native void ensureRavenwoodInitialized$ravenwood();
+    private static void ensureRavenwoodInitialized$ravenwood() {
+        nativeEnsureRavenwoodInitialized();
+    }
 
     /**
      * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
@@ -92,7 +94,9 @@
         throw notSupportedOnDevice();
     }
 
-    private native <T> T fromAddress$ravenwood(long address);
+    private <T> T fromAddress$ravenwood(long address) {
+        return nativeFromAddress(address);
+    }
 
     /**
      * See {@link Workaround}. It's only usable on Ravenwood.
@@ -114,7 +118,14 @@
         throw notSupportedOnDevice();
     }
 
-    private native String getRavenwoodRuntimePath$ravenwood();
+    private String getRavenwoodRuntimePath$ravenwood() {
+        return nativeGetRavenwoodRuntimePath();
+    }
+
+    // Private native methods that are actually substituted on Ravenwood
+    private native <T> T nativeFromAddress(long address);
+    private native String nativeGetRavenwoodRuntimePath();
+    private static native void nativeEnsureRavenwoodInitialized();
 
     /**
      * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 1d43f6f..c834dde 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -209,11 +209,6 @@
     void onDisplayReady(int displayId);
 
     /**
-     * Notifies System UI whether the recents animation is running or not.
-     */
-    void onRecentsAnimationStateChanged(boolean running);
-
-    /**
      * Notifies System UI side of system bar attribute change on the specified display.
      *
      * @param displayId the ID of the display to notify.
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index 76ce452..1938cdb 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,22 @@
         // 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 enum Shape {
+        /**
+         * Icon view should use WRAP_CONTENT -- so that the horizontal space occupied depends on the
+         * icon's shape (skinny/fat icons take less/more). Most icons will want to use this option
+         * for a nicer-looking overall spacing in the status bar, as long as the icon is "known"
+         * (i.e. not coming from a 3P package).
+         */
+        WRAP_CONTENT,
+
+        /** Icon should always be displayed in a space as wide as the status bar is tall. */
+        FIXED_SPACE,
     }
 
     public UserHandle user;
@@ -45,9 +72,17 @@
     public int number;
     public CharSequence contentDescription;
     public Type type;
+    public Shape shape;
+
+    /**
+     * 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) {
+            CharSequence contentDescription, Type type, Shape shape) {
         if (icon.getType() == Icon.TYPE_RESOURCE
                 && TextUtils.isEmpty(icon.getResPackage())) {
             // This is an odd situation where someone's managed to hand us an icon without a
@@ -62,6 +97,13 @@
         this.number = number;
         this.contentDescription = contentDescription;
         this.type = type;
+        this.shape = shape;
+    }
+
+    public StatusBarIcon(UserHandle user, String resPackage, Icon icon, int iconLevel, int number,
+            CharSequence contentDescription, Type type) {
+        this(user, resPackage, icon, iconLevel, number, contentDescription, type,
+                Shape.WRAP_CONTENT);
     }
 
     public StatusBarIcon(String iconPackage, UserHandle user,
@@ -86,8 +128,9 @@
     @Override
     public StatusBarIcon clone() {
         StatusBarIcon that = new StatusBarIcon(this.user, this.pkg, this.icon,
-                this.iconLevel, this.number, this.contentDescription, this.type);
+                this.iconLevel, this.number, this.contentDescription, this.type, this.shape);
         that.visible = this.visible;
+        that.preloadedIcon = this.preloadedIcon;
         return that;
     }
 
@@ -107,6 +150,7 @@
         this.number = in.readInt();
         this.contentDescription = in.readCharSequence();
         this.type = Type.valueOf(in.readString());
+        this.shape = Shape.valueOf(in.readString());
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -118,6 +162,7 @@
         out.writeInt(this.number);
         out.writeCharSequence(this.contentDescription);
         out.writeString(this.type.name());
+        out.writeString(this.shape.name());
     }
 
     public int describeContents() {
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 1e2cad4..1e965c5d 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -49,81 +49,41 @@
 
     private ArrayUtils() { /* cannot be instantiated */ }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     public static byte[] newUnpaddedByteArray(int minLen) {
         return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen);
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     public static char[] newUnpaddedCharArray(int minLen) {
         return (char[])VMRuntime.getRuntime().newUnpaddedArray(char.class, minLen);
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static int[] newUnpaddedIntArray(int minLen) {
         return (int[])VMRuntime.getRuntime().newUnpaddedArray(int.class, minLen);
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     public static boolean[] newUnpaddedBooleanArray(int minLen) {
         return (boolean[])VMRuntime.getRuntime().newUnpaddedArray(boolean.class, minLen);
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     public static long[] newUnpaddedLongArray(int minLen) {
         return (long[])VMRuntime.getRuntime().newUnpaddedArray(long.class, minLen);
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     public static float[] newUnpaddedFloatArray(int minLen) {
         return (float[])VMRuntime.getRuntime().newUnpaddedArray(float.class, minLen);
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     public static Object[] newUnpaddedObjectArray(int minLen) {
         return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen);
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @SuppressWarnings("unchecked")
     public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
         return (T[])VMRuntime.getRuntime().newUnpaddedArray(clazz, minLen);
     }
 
-    public static byte[] newUnpaddedByteArray$ravenwood(int minLen) {
-        return new byte[minLen];
-    }
-
-    public static char[] newUnpaddedCharArray$ravenwood(int minLen) {
-        return new char[minLen];
-    }
-
-    public static int[] newUnpaddedIntArray$ravenwood(int minLen) {
-        return new int[minLen];
-    }
-
-    public static boolean[] newUnpaddedBooleanArray$ravenwood(int minLen) {
-        return new boolean[minLen];
-    }
-
-    public static long[] newUnpaddedLongArray$ravenwood(int minLen) {
-        return new long[minLen];
-    }
-
-    public static float[] newUnpaddedFloatArray$ravenwood(int minLen) {
-        return new float[minLen];
-    }
-
-    public static Object[] newUnpaddedObjectArray$ravenwood(int minLen) {
-        return new Object[minLen];
-    }
-
-    public static <T> T[] newUnpaddedArray$ravenwood(Class<T> clazz, int minLen) {
-        return (T[]) Array.newInstance(clazz, minLen);
-    }
-
     /**
      * Checks if the beginnings of two byte arrays are equal.
      *
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index b51678e..efbf887 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -76,10 +76,10 @@
 
     boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
             in ImeTracker.Token statsToken, int flags, int lastClickToolType,
-            in @nullable ResultReceiver resultReceiver, int reason);
+            in @nullable ResultReceiver resultReceiver, int reason, boolean async);
     boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
             in ImeTracker.Token statsToken, int flags,
-            in @nullable ResultReceiver resultReceiver, int reason);
+            in @nullable ResultReceiver resultReceiver, int reason, boolean async);
 
     /**
      * A test API for CTS to request hiding the current soft input window, with the request origin
@@ -120,7 +120,8 @@
             in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection,
             in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             int unverifiedTargetSdkVersion, int userId,
-            in ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq);
+            in ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
+            boolean useAsyncShowHideMethod);
 
     void showInputMethodPickerFromClient(in IInputMethodClient client,
             int auxiliarySubtypeMode);
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
index bc729f1..b6383d9 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
@@ -437,9 +437,8 @@
 
         // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
         // landscape.
-        final int x = Math.min(
-                contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
-                mViewPortOnScreen.right - mPopupWindow.getWidth());
+        final int x = Math.clamp(contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2,
+                mViewPortOnScreen.left, mViewPortOnScreen.right - mPopupWindow.getWidth());
 
         final int y;
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2abdd57..90cb10a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -108,6 +108,7 @@
         "libtracing_perfetto",
         "libharfbuzz_ng",
         "liblog",
+        "libmediautils",
         "libminikin",
         "libz",
         "server_configurable_flags",
diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp
index 50832a5..8dd63cc 100644
--- a/core/jni/android_graphics_SurfaceTexture.cpp
+++ b/core/jni/android_graphics_SurfaceTexture.cpp
@@ -256,9 +256,21 @@
     }
 }
 
-static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
-        jint texName, jboolean singleBufferMode, jobject weakThiz)
-{
+static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached, jint texName,
+                                jboolean singleBufferMode, jobject weakThiz) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    sp<SurfaceTexture> surfaceTexture;
+    if (isDetached) {
+        surfaceTexture = new SurfaceTexture(GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
+    } else {
+        surfaceTexture =
+                new SurfaceTexture(texName, GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
+    }
+
+    if (singleBufferMode) {
+        surfaceTexture->setMaxBufferCount(1);
+    }
+#else
     sp<IGraphicBufferProducer> producer;
     sp<IGraphicBufferConsumer> consumer;
     BufferQueue::createBufferQueue(&producer, &consumer);
@@ -275,6 +287,7 @@
         surfaceTexture = new SurfaceTexture(consumer, texName,
                 GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
     }
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
     if (surfaceTexture == 0) {
         jniThrowException(env, OutOfResourcesException,
@@ -287,11 +300,27 @@
             createProcessUniqueId()));
 
     // If the current context is protected, inform the producer.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    surfaceTexture->setConsumerIsProtected(isProtectedContext());
+
+    SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
+    sp<Surface> surface = surfaceTexture->getSurface();
+    if (nullptr == surface) {
+        jniThrowException(env, IllegalStateException, "Unable to get surface from SurfaceTexture");
+        return;
+    }
+    sp<IGraphicBufferProducer> igbp = surface->getIGraphicBufferProducer();
+    if (nullptr == igbp) {
+        jniThrowException(env, IllegalStateException, "Unable to get IGBP from Surface");
+        return;
+    }
+    SurfaceTexture_setProducer(env, thiz, igbp);
+#else
     consumer->setConsumerIsProtected(isProtectedContext());
 
     SurfaceTexture_setSurfaceTexture(env, thiz, surfaceTexture);
     SurfaceTexture_setProducer(env, thiz, producer);
-
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     jclass clazz = env->GetObjectClass(thiz);
     if (clazz == NULL) {
         jniThrowRuntimeException(env,
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 638591f..9bccf5a 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -17,6 +17,7 @@
 
 //#define LOG_NDEBUG 0
 
+#include <atomic>
 #define LOG_TAG "AudioSystem-JNI"
 #include <android/binder_ibinder_jni.h>
 #include <android/binder_libbinder.h>
@@ -34,15 +35,16 @@
 #include <media/AudioContainers.h>
 #include <media/AudioPolicy.h>
 #include <media/AudioSystem.h>
+#include <mediautils/jthread.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/jni_macros.h>
 #include <system/audio.h>
 #include <system/audio_policy.h>
+#include <sys/system_properties.h>
 #include <utils/Log.h>
 
-#include <thread>
 #include <optional>
 #include <sstream>
 #include <memory>
@@ -57,6 +59,7 @@
 #include "android_media_AudioMixerAttributes.h"
 #include "android_media_AudioProfile.h"
 #include "android_media_MicrophoneInfo.h"
+#include "android_media_JNIUtils.h"
 #include "android_util_Binder.h"
 #include "core_jni_helpers.h"
 
@@ -3375,42 +3378,48 @@
 class JavaSystemPropertyListener {
   public:
     JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) :
-            mCallback(env->NewGlobalRef(javaCallback)),
-            mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) {
-        mListenerThread = std::thread([this]() mutable {
-            JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm);
-            while (!mCleanupSignal.load()) {
-                using namespace std::chrono_literals;
-                // 1s timeout so this thread can read the cleanup signal to (slowly) be able to
-                // be destroyed.
-                std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
-                if (newVal != "" && mLastVal != newVal) {
-                    threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run);
-                    mLastVal = std::move(newVal);
+            mCallback {javaCallback, env},
+            mSysPropName(sysPropName),
+            mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}),
+            mListenerThread([this](mediautils::stop_token stok) mutable {
+                while (!stok.stop_requested()) {
+                    using namespace std::chrono_literals;
+                    // 1s timeout so this thread can eventually respond to the stop token
+                    std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
+                    updateValue(newVal);
                 }
-            }
-            });
-    }
+            }) {}
 
-    ~JavaSystemPropertyListener() {
-        mCleanupSignal.store(true);
-        mListenerThread.join();
-        JNIEnv* env = GetOrAttachJNIEnvironment(gVm);
-        env->DeleteGlobalRef(mCallback);
+    void triggerUpdateIfChanged() {
+        // We must check the property without using the cached property due to thread safety issues
+        std::string newVal = base::GetProperty(mSysPropName, "");
+        updateValue(newVal);
     }
 
   private:
-    jobject mCallback;
+    void updateValue(std::string newVal) {
+        if (newVal == "") return;
+        std::lock_guard l{mLock};
+        if (mLastVal == newVal) return;
+        const auto threadEnv = GetOrAttachJNIEnvironment(gVm);
+        threadEnv->CallVoidMethod(mCallback.get(), gRunnableClassInfo.run);
+        mLastVal = std::move(newVal);
+    }
+
+    // Should outlive thread object
+    const GlobalRef mCallback;
+    const std::string mSysPropName;
     android::base::CachedProperty mCachedProperty;
-    std::thread mListenerThread;
-    std::atomic<bool> mCleanupSignal{false};
     std::string mLastVal = "";
+    std::mutex mLock;
+    const mediautils::jthread mListenerThread;
 };
 
+// A logical set keyed by address
 std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners;
 std::mutex gSysPropLock{};
 
-static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env,  jobject thiz,
+static jlong android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env,  jobject thiz,
         jstring sysProp,
         jobject javaCallback) {
     ScopedUtfChars sysPropChars{env, sysProp};
@@ -3418,6 +3427,19 @@
             std::string{sysPropChars.c_str()});
     std::unique_lock _l{gSysPropLock};
     gSystemPropertyListeners.push_back(std::move(listener));
+    return reinterpret_cast<jlong>(gSystemPropertyListeners.back().get());
+}
+
+static void android_media_AudioSystem_triggerSystemPropertyUpdate(JNIEnv *env,  jobject thiz,
+        jlong nativeHandle) {
+    std::unique_lock _l{gSysPropLock};
+    const auto iter = std::find_if(gSystemPropertyListeners.begin(), gSystemPropertyListeners.end(),
+            [nativeHandle](const auto& x) { return reinterpret_cast<jlong>(x.get()) == nativeHandle; });
+    if (iter != gSystemPropertyListeners.end()) {
+        (*iter)->triggerUpdateIfChanged();
+    } else {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid handle");
+    }
 }
 
 
@@ -3595,8 +3617,11 @@
          MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
          MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
          MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
-                                "(Ljava/lang/String;Ljava/lang/Runnable;)V",
+                                "(Ljava/lang/String;Ljava/lang/Runnable;)J",
                                 android_media_AudioSystem_listenForSystemPropertyChange),
+         MAKE_JNI_NATIVE_METHOD("triggerSystemPropertyUpdate",
+                                "(J)V",
+                                android_media_AudioSystem_triggerSystemPropertyUpdate),
 
         };
 
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 2068bd7..921b77d 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -56,11 +56,11 @@
 //#undef ALOGV
 //#define ALOGV(...) fprintf(stderr, __VA_ARGS__)
 
-#define DEBUG_DEATH 0
-#if DEBUG_DEATH
-#define LOGDEATH ALOGD
+#define DEBUG_DEATH_FREEZE 0
+#if DEBUG_DEATH_FREEZE
+#define LOG_DEATH_FREEZE ALOGD
 #else
-#define LOGDEATH ALOGV
+#define LOG_DEATH_FREEZE ALOGV
 #endif
 
 using namespace android;
@@ -116,6 +116,7 @@
     jclass mClass;
     jmethodID mGetInstance;
     jmethodID mSendDeathNotice;
+    jmethodID mInvokeFrozenStateChangeCallback;
 
     // Object state.
     jfieldID mNativeData;  // Field holds native pointer to BinderProxyNativeData.
@@ -466,10 +467,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());
             }
@@ -532,23 +548,59 @@
 
 // ----------------------------------------------------------------------------
 
-// Per-IBinder death recipient bookkeeping.  This is how we reconcile local jobject
-// death recipient references passed in through JNI with the permanent corresponding
-// JavaDeathRecipient objects.
+// A JavaRecipient receives either death notifications or frozen state change
+// callbacks from natve code (IBinder) and dispatch the notifications to its
+// corresponding Java listener object.
+//
+// A RecipientList keeps tracks of all JavaRecipients for an IBinder. This way
+// we can find a JavaRecipient given a Java listener object.
+//
+// The implementation is shared between death recipients and frozen state change
+// callbacks via template. For death recipients the template is instantiated as
+// follows:
+//
+//                   IBinder::DeathRecipient
+//                            ^
+//                            |
+//                        (inherits)
+//                            |
+//            JavaRecipient<IBinder::DeathRecipient> <----> RecipientList<IBinder::DeathRecipient>
+//                            ^
+//                            |
+//                        (inherits)
+//                            |
+//                    JavaDeathRecipient
+//
+//
+// The instantiation for frozen state change callbacks are:
+//
+//             IBinder::FrozenStateChangeCallback
+//                           ^
+//                           |
+//                       (inherits)
+//                           |
+//     JavaRecipient<IBinder::FrozenStateChangeCallback>
+//                           ^                ^
+//                           |                |
+//                       (inherits)           +--> RecipientList<IBinder::FrozenStateChangeCallback>
+//                           |
+//              JavaFrozenStateChangeCallback
 
-class JavaDeathRecipient;
+template <typename T>
+class JavaRecipient;
 
-class DeathRecipientList : public RefBase {
-    List< sp<JavaDeathRecipient> > mList;
+template <typename T>
+class RecipientList : public RefBase {
+    List<sp<JavaRecipient<T> > > mList;
     Mutex mLock;
 
 public:
-    DeathRecipientList();
-    ~DeathRecipientList();
+    RecipientList();
+    ~RecipientList();
 
-    void add(const sp<JavaDeathRecipient>& recipient);
-    void remove(const sp<JavaDeathRecipient>& recipient);
-    sp<JavaDeathRecipient> find(jobject recipient);
+    void add(const sp<JavaRecipient<T> >& recipient);
+    void remove(const sp<JavaRecipient<T> >& recipient);
+    sp<JavaRecipient<T> > find(jobject recipient);
 
     Mutex& lock();  // Use with care; specifically for mutual exclusion during binder death
 };
@@ -569,11 +621,113 @@
 #endif // __BIONIC__
 #endif // BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI
 
-class JavaDeathRecipient : public IBinder::DeathRecipient
-{
+template <typename T>
+constexpr const char* logPrefix();
+
+template <>
+constexpr const char* logPrefix<IBinder::DeathRecipient>() {
+    return "[DEATH]";
+}
+
+template <>
+constexpr const char* logPrefix<IBinder::FrozenStateChangeCallback>() {
+    return "[FREEZE]";
+}
+
+template <typename T>
+class JavaRecipient : public T {
 public:
-    JavaDeathRecipient(JNIEnv* env, jobject object, const sp<DeathRecipientList>& list)
+    JavaRecipient(JNIEnv* env, jobject object, const sp<RecipientList<T> >& list,
+                  bool useWeakReference)
           : mVM(jnienv_to_javavm(env)), mObject(NULL), mObjectWeak(NULL), mList(list) {
+        if (useWeakReference) {
+            mObjectWeak = env->NewWeakGlobalRef(object);
+        } else {
+            mObject = env->NewGlobalRef(object);
+        }
+        // These objects manage their own lifetimes so are responsible for final bookkeeping.
+        // The list holds a strong reference to this object.
+        LOG_DEATH_FREEZE("%s Adding JavaRecipient %p to RecipientList %p", logPrefix<T>(), this,
+                         list.get());
+        list->add(this);
+    }
+
+    void clearReference() {
+        sp<RecipientList<T> > list = mList.promote();
+        if (list != NULL) {
+            LOG_DEATH_FREEZE("%s Removing JavaRecipient %p from RecipientList %p", logPrefix<T>(),
+                             this, list.get());
+            list->remove(this);
+        } else {
+            LOG_DEATH_FREEZE("%s clearReference() on JavaRecipient %p but RecipientList wp purged",
+                             logPrefix<T>(), this);
+        }
+    }
+
+    bool matches(jobject obj) {
+        bool result;
+        JNIEnv* env = javavm_to_jnienv(mVM);
+
+        if (mObject != NULL) {
+            result = env->IsSameObject(obj, mObject);
+        } else {
+            ScopedLocalRef<jobject> me(env, env->NewLocalRef(mObjectWeak));
+            result = env->IsSameObject(obj, me.get());
+        }
+        return result;
+    }
+
+    void warnIfStillLive() {
+        if (mObject != NULL) {
+            // Okay, something is wrong -- we have a hard reference to a live death
+            // recipient on the VM side, but the list is being torn down.
+            JNIEnv* env = javavm_to_jnienv(mVM);
+            ScopedLocalRef<jclass> objClassRef(env, env->GetObjectClass(mObject));
+            ScopedLocalRef<jstring> nameRef(env,
+                                            (jstring)env->CallObjectMethod(objClassRef.get(),
+                                                                           gClassOffsets.mGetName));
+            ScopedUtfChars nameUtf(env, nameRef.get());
+            if (nameUtf.c_str() != NULL) {
+                ALOGW("BinderProxy is being destroyed but the application did not call "
+                      "unlinkToDeath to unlink all of its death recipients beforehand.  "
+                      "Releasing leaked death recipient: %s",
+                      nameUtf.c_str());
+            } else {
+                ALOGW("BinderProxy being destroyed; unable to get DR object name");
+                env->ExceptionClear();
+            }
+        }
+    }
+
+protected:
+    virtual ~JavaRecipient() {
+        // ALOGI("Removing death ref: recipient=%p\n", mObject);
+        JNIEnv* env = javavm_to_jnienv(mVM);
+        if (mObject != NULL) {
+            env->DeleteGlobalRef(mObject);
+        } else {
+            env->DeleteWeakGlobalRef(mObjectWeak);
+        }
+    }
+
+    JavaVM* const mVM;
+
+    // If useWeakReference is false (e.g. JavaDeathRecipient when target sdk version < 35), the
+    // Java-side Recipient is strongly referenced from mObject initially, and may later be demoted
+    // to a weak reference (mObjectWeak), e.g. upon linkToDeath() and then after binderDied() is
+    // called.
+    // If useWeakReference is true, the strong reference is never made here (i.e. mObject == NULL
+    // always). Instead, the strong reference to the Java-side Recipient is made in
+    // BinderProxy.{mDeathRecipients,mFrozenStateChangeCallbacks}. In the native world, only the
+    // weak reference is kept.
+    jobject mObject;
+    jweak mObjectWeak;
+    wp<RecipientList<T> > mList;
+};
+
+class JavaDeathRecipient : public JavaRecipient<IBinder::DeathRecipient> {
+public:
+    static bool useWeakReference() {
         // b/298374304: For apps targeting Android V or beyond, we no longer hold the global JNI ref
         // to the death recipient objects. This is to prevent the memory leak which can happen when
         // the death recipient object internally has a strong reference to the proxy object. Under
@@ -589,25 +743,26 @@
         // reference to. If however you want to get binderDied() regardless of the proxy object's
         // lifecycle, keep a strong reference to the death recipient object by yourself.
 #ifdef BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI
-        if (target_sdk_is_at_least_vic()) {
-            mObjectWeak = env->NewWeakGlobalRef(object);
-        } else
+        return target_sdk_is_at_least_vic();
+#else
+        return false;
 #endif
-        {
-            mObject = env->NewGlobalRef(object);
-        }
-        // These objects manage their own lifetimes so are responsible for final bookkeeping.
-        // The list holds a strong reference to this object.
-        LOGDEATH("Adding JDR %p to DRL %p", this, list.get());
-        list->add(this);
+    }
 
+    JavaDeathRecipient(JNIEnv* env, jobject object,
+                       const sp<RecipientList<IBinder::DeathRecipient> >& list)
+          : JavaRecipient(env, object, list, useWeakReference()) {
         gNumDeathRefsCreated.fetch_add(1, std::memory_order_relaxed);
         gcIfManyNewRefs(env);
     }
 
+    ~JavaDeathRecipient() {
+        gNumDeathRefsDeleted.fetch_add(1, std::memory_order_relaxed);
+    }
+
     void binderDied(const wp<IBinder>& who)
     {
-        LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this);
+        LOG_DEATH_FREEZE("Receiving binderDied() on JavaDeathRecipient %p\n", this);
         if (mObject == NULL && mObjectWeak == NULL) {
             return;
         }
@@ -647,7 +802,7 @@
         // with our containing DeathRecipientList so that we can't delete the global ref on mObject
         // while the list is being iterated.
         if (mObject != NULL) {
-            sp<DeathRecipientList> list = mList.promote();
+            auto list = mList.promote();
             if (list != NULL) {
                 AutoMutex _l(list->lock());
 
@@ -658,126 +813,96 @@
         }
     }
 
-    void clearReference()
-    {
-        sp<DeathRecipientList> list = mList.promote();
-        if (list != NULL) {
-            LOGDEATH("Removing JDR %p from DRL %p", this, list.get());
-            list->remove(this);
-        } else {
-            LOGDEATH("clearReference() on JDR %p but DRL wp purged", this);
-        }
-    }
-
-    bool matches(jobject obj) {
-        bool result;
-        JNIEnv* env = javavm_to_jnienv(mVM);
-
-        if (mObject != NULL) {
-            result = env->IsSameObject(obj, mObject);
-        } else {
-            ScopedLocalRef<jobject> me(env, env->NewLocalRef(mObjectWeak));
-            result = env->IsSameObject(obj, me.get());
-        }
-        return result;
-    }
-
-    void warnIfStillLive() {
-        if (mObject != NULL) {
-            // Okay, something is wrong -- we have a hard reference to a live death
-            // recipient on the VM side, but the list is being torn down.
-            JNIEnv* env = javavm_to_jnienv(mVM);
-            ScopedLocalRef<jclass> objClassRef(env, env->GetObjectClass(mObject));
-            ScopedLocalRef<jstring> nameRef(env,
-                    (jstring) env->CallObjectMethod(objClassRef.get(), gClassOffsets.mGetName));
-            ScopedUtfChars nameUtf(env, nameRef.get());
-            if (nameUtf.c_str() != NULL) {
-                ALOGW("BinderProxy is being destroyed but the application did not call "
-                        "unlinkToDeath to unlink all of its death recipients beforehand.  "
-                        "Releasing leaked death recipient: %s", nameUtf.c_str());
-            } else {
-                ALOGW("BinderProxy being destroyed; unable to get DR object name");
-                env->ExceptionClear();
-            }
-        }
-    }
-
-protected:
-    virtual ~JavaDeathRecipient()
-    {
-        //ALOGI("Removing death ref: recipient=%p\n", mObject);
-        gNumDeathRefsDeleted.fetch_add(1, std::memory_order_relaxed);
-        JNIEnv* env = javavm_to_jnienv(mVM);
-        if (mObject != NULL) {
-            env->DeleteGlobalRef(mObject);
-        } else {
-            env->DeleteWeakGlobalRef(mObjectWeak);
-        }
-    }
-
 private:
-    JavaVM* const mVM;
-
-    // If target sdk version < 35, the Java-side DeathRecipient is strongly referenced from mObject
-    // upon linkToDeath() and then after binderDied() is called, the strong reference is demoted to
-    // a weak reference (mObjectWeak).
-    // If target sdk version >= 35, the strong reference is never made here (i.e. mObject == NULL
-    // always). Instead, the strong reference to the Java-side DeathRecipient is made in
-    // BinderProxy.mDeathRecipients. In the native world, only the weak reference is kept.
-    jobject mObject;
-    jweak mObjectWeak;
-    wp<DeathRecipientList> mList;
-
     // Whether binderDied was called or not.
     bool mFired = false;
 };
 
+class JavaFrozenStateChangeCallback : public JavaRecipient<IBinder::FrozenStateChangeCallback> {
+public:
+    JavaFrozenStateChangeCallback(
+            JNIEnv* env, jobject object,
+            const sp<RecipientList<IBinder::FrozenStateChangeCallback> >& list)
+          : JavaRecipient(env, object, list, /*useWeakReference=*/true) {}
+
+    void onStateChanged(const wp<IBinder>& who, State state) {
+        LOG_DEATH_FREEZE("Receiving onStateChanged() on JavaFrozenStateChangeCallback %p. state: "
+                         "%s\n",
+                         this, state == State::FROZEN ? "FROZEN" : "UNFROZEN");
+        if (mObjectWeak == NULL) {
+            return;
+        }
+        JNIEnv* env = javavm_to_jnienv(mVM);
+        ScopedLocalRef<jobject> jBinderProxy(env, javaObjectForIBinder(env, who.promote()));
+
+        // Hold a local reference to the recipient. This may fail if the recipient is weakly
+        // referenced, in which case we can't deliver the notification.
+        ScopedLocalRef<jobject> jCallback(env, env->NewLocalRef(mObjectWeak));
+        if (jCallback.get() == NULL) {
+            return;
+        }
+        env->CallStaticVoidMethod(gBinderProxyOffsets.mClass,
+                                  gBinderProxyOffsets.mInvokeFrozenStateChangeCallback,
+                                  jCallback.get(), jBinderProxy.get(), state);
+        if (env->ExceptionCheck()) {
+            jthrowable excep = env->ExceptionOccurred();
+            binder_report_exception(env, excep,
+                                    "*** Uncaught exception returned from frozen state change "
+                                    "notification!");
+        }
+    }
+};
+
 // ----------------------------------------------------------------------------
 
-DeathRecipientList::DeathRecipientList() {
-    LOGDEATH("New DRL @ %p", this);
+template <typename T>
+RecipientList<T>::RecipientList() {
+    LOG_DEATH_FREEZE("%s New RecipientList @ %p", logPrefix<T>(), this);
 }
 
-DeathRecipientList::~DeathRecipientList() {
-    LOGDEATH("Destroy DRL @ %p", this);
+template <typename T>
+RecipientList<T>::~RecipientList() {
+    LOG_DEATH_FREEZE("%s Destroy RecipientList @ %p", logPrefix<T>(), this);
     AutoMutex _l(mLock);
 
-    // Should never happen -- the JavaDeathRecipient objects that have added themselves
+    // Should never happen -- the JavaRecipientList objects that have added themselves
     // to the list are holding references on the list object.  Only when they are torn
     // down can the list header be destroyed.
     if (mList.size() > 0) {
-        List< sp<JavaDeathRecipient> >::iterator iter;
-        for (iter = mList.begin(); iter != mList.end(); iter++) {
+        for (auto iter = mList.begin(); iter != mList.end(); iter++) {
             (*iter)->warnIfStillLive();
         }
     }
 }
 
-void DeathRecipientList::add(const sp<JavaDeathRecipient>& recipient) {
+template <typename T>
+void RecipientList<T>::add(const sp<JavaRecipient<T> >& recipient) {
     AutoMutex _l(mLock);
 
-    LOGDEATH("DRL @ %p : add JDR %p", this, recipient.get());
+    LOG_DEATH_FREEZE("%s RecipientList @ %p : add JavaRecipient %p", logPrefix<T>(), this,
+                     recipient.get());
     mList.push_back(recipient);
 }
 
-void DeathRecipientList::remove(const sp<JavaDeathRecipient>& recipient) {
+template <typename T>
+void RecipientList<T>::remove(const sp<JavaRecipient<T> >& recipient) {
     AutoMutex _l(mLock);
 
-    List< sp<JavaDeathRecipient> >::iterator iter;
-    for (iter = mList.begin(); iter != mList.end(); iter++) {
+    for (auto iter = mList.begin(); iter != mList.end(); iter++) {
         if (*iter == recipient) {
-            LOGDEATH("DRL @ %p : remove JDR %p", this, recipient.get());
+            LOG_DEATH_FREEZE("%s RecipientList @ %p : remove JavaRecipient %p", logPrefix<T>(),
+                             this, recipient.get());
             mList.erase(iter);
             return;
         }
     }
 }
 
-sp<JavaDeathRecipient> DeathRecipientList::find(jobject recipient) {
+template <typename T>
+sp<JavaRecipient<T> > RecipientList<T>::find(jobject recipient) {
     AutoMutex _l(mLock);
 
-    List< sp<JavaDeathRecipient> >::iterator iter;
-    for (iter = mList.begin(); iter != mList.end(); iter++) {
+    for (auto iter = mList.begin(); iter != mList.end(); iter++) {
         if ((*iter)->matches(recipient)) {
             return *iter;
         }
@@ -785,10 +910,14 @@
     return NULL;
 }
 
-Mutex& DeathRecipientList::lock() {
+template <typename T>
+Mutex& RecipientList<T>::lock() {
     return mLock;
 }
 
+using DeathRecipientList = RecipientList<IBinder::DeathRecipient>;
+using FrozenStateChangeCallbackList = RecipientList<IBinder::FrozenStateChangeCallback>;
+
 // ----------------------------------------------------------------------------
 
 namespace android {
@@ -806,6 +935,11 @@
     // Death recipients for mObject. Reference counted only because DeathRecipients
     // hold a weak reference that can be temporarily promoted.
     sp<DeathRecipientList> mOrgue;  // Death recipients for mObject.
+
+    // Frozen state change callbacks for mObject. Reference counted only because
+    // JavaFrozenStateChangeCallback hold a weak reference that can be
+    // temporarily promoted.
+    sp<FrozenStateChangeCallbackList> mFrozenStateChangCallbackList;
 };
 
 BinderProxyNativeData* getBPNativeData(JNIEnv* env, jobject obj) {
@@ -825,12 +959,13 @@
     if (val->checkSubclass(&gBinderOffsets)) {
         // It's a JavaBBinder created by ibinderForJavaObject. Already has Java object.
         jobject object = static_cast<JavaBBinder*>(val.get())->object();
-        LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);
+        LOG_DEATH_FREEZE("objectForBinder %p: it's our own %p!\n", val.get(), object);
         return object;
     }
 
     BinderProxyNativeData* nativeData = new BinderProxyNativeData();
     nativeData->mOrgue = new DeathRecipientList;
+    nativeData->mFrozenStateChangCallbackList = new FrozenStateChangeCallbackList;
     nativeData->mObject = val;
 
     jobject object = env->CallStaticObjectMethod(gBinderProxyOffsets.mClass,
@@ -1433,7 +1568,7 @@
     BinderProxyNativeData *nd = getBPNativeData(env, obj);
     IBinder* target = nd->mObject.get();
 
-    LOGDEATH("linkToDeath: binder=%p recipient=%p\n", target, recipient);
+    LOG_DEATH_FREEZE("linkToDeath: binder=%p recipient=%p\n", target, recipient);
 
     if (!target->localBinder()) {
         DeathRecipientList* list = nd->mOrgue.get();
@@ -1464,15 +1599,15 @@
         return JNI_FALSE;
     }
 
-    LOGDEATH("unlinkToDeath: binder=%p recipient=%p\n", target, recipient);
+    LOG_DEATH_FREEZE("unlinkToDeath: binder=%p recipient=%p\n", target, recipient);
 
     if (!target->localBinder()) {
         status_t err = NAME_NOT_FOUND;
 
         // If we find the matching recipient, proceed to unlink using that
         DeathRecipientList* list = nd->mOrgue.get();
-        sp<JavaDeathRecipient> origJDR = list->find(recipient);
-        LOGDEATH("   unlink found list %p and JDR %p", list, origJDR.get());
+        sp<JavaRecipient<IBinder::DeathRecipient> > origJDR = list->find(recipient);
+        LOG_DEATH_FREEZE("   unlink found list %p and JDR %p", list, origJDR.get());
         if (origJDR != NULL) {
             wp<IBinder::DeathRecipient> dr;
             err = target->unlinkToDeath(origJDR, NULL, flags, &dr);
@@ -1498,11 +1633,85 @@
     return res;
 }
 
+static void android_os_BinderProxy_addFrozenStateChangeCallback(
+        JNIEnv* env, jobject obj,
+        jobject callback) // throws RemoteException
+{
+    if (callback == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return;
+    }
+
+    BinderProxyNativeData* nd = getBPNativeData(env, obj);
+    IBinder* target = nd->mObject.get();
+
+    LOG_DEATH_FREEZE("addFrozenStateChangeCallback: binder=%p callback=%p\n", target, callback);
+
+    if (!target->localBinder()) {
+        FrozenStateChangeCallbackList* list = nd->mFrozenStateChangCallbackList.get();
+        auto jfscc = sp<JavaFrozenStateChangeCallback>::make(env, callback, list);
+        status_t err = target->addFrozenStateChangeCallback(jfscc);
+        if (err != NO_ERROR) {
+            // Failure adding the callback, so clear its reference now.
+            jfscc->clearReference();
+            signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/);
+        }
+    }
+}
+
+static jboolean android_os_BinderProxy_removeFrozenStateChangeCallback(JNIEnv* env, jobject obj,
+                                                                       jobject callback) {
+    jboolean res = JNI_FALSE;
+    if (callback == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return res;
+    }
+
+    BinderProxyNativeData* nd = getBPNativeData(env, obj);
+    IBinder* target = nd->mObject.get();
+    if (target == NULL) {
+        ALOGW("Binder has been finalized when calling removeFrozenStateChangeCallback() with "
+              "callback=%p)\n",
+              callback);
+        return JNI_FALSE;
+    }
+
+    LOG_DEATH_FREEZE("removeFrozenStateChangeCallback: binder=%p callback=%p\n", target, callback);
+
+    if (!target->localBinder()) {
+        status_t err = NAME_NOT_FOUND;
+
+        // If we find the matching callback, proceed to unlink using that
+        FrozenStateChangeCallbackList* list = nd->mFrozenStateChangCallbackList.get();
+        sp<JavaRecipient<IBinder::FrozenStateChangeCallback> > origJFSCC = list->find(callback);
+        LOG_DEATH_FREEZE("   removeFrozenStateChangeCallback found list %p and JFSCC %p", list,
+                         origJFSCC.get());
+        if (origJFSCC != NULL) {
+            err = target->removeFrozenStateChangeCallback(origJFSCC);
+            if (err == NO_ERROR) {
+                origJFSCC->clearReference();
+            }
+        }
+
+        if (err == NO_ERROR || err == DEAD_OBJECT) {
+            res = JNI_TRUE;
+        } else {
+            jniThrowException(env, "java/util/NoSuchElementException",
+                              base::StringPrintf("Frozen state change callback does not exist (%s)",
+                                                 statusToString(err).c_str())
+                                      .c_str());
+        }
+    }
+
+    return res;
+}
+
 static void BinderProxy_destroy(void* rawNativeData)
 {
     BinderProxyNativeData * nativeData = (BinderProxyNativeData *) rawNativeData;
-    LOGDEATH("Destroying BinderProxy: binder=%p drl=%p\n",
-            nativeData->mObject.get(), nativeData->mOrgue.get());
+    LOG_DEATH_FREEZE("Destroying BinderProxy: binder=%p drl=%p fsccl=%p\n",
+                     nativeData->mObject.get(), nativeData->mOrgue.get(),
+                     nativeData->mFrozenStateChangCallbackList.get());
     delete nativeData;
     IPCThreadState::self()->flushCommands();
 }
@@ -1537,6 +1746,10 @@
     {"transactNative",      "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},
     {"linkToDeathNative",   "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
     {"unlinkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
+    {"addFrozenStateChangeCallbackNative",
+        "(Landroid/os/IBinder$IFrozenStateChangeCallback;)V", (void*)android_os_BinderProxy_addFrozenStateChangeCallback},
+    {"removeFrozenStateChangeCallbackNative",
+        "(Landroid/os/IBinder$IFrozenStateChangeCallback;)Z", (void*)android_os_BinderProxy_removeFrozenStateChangeCallback},
     {"getNativeFinalizer",  "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
     {"getExtension",        "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension},
 };
@@ -1559,6 +1772,10 @@
     gBinderProxyOffsets.mSendDeathNotice =
             GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
                                    "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V");
+    gBinderProxyOffsets.mInvokeFrozenStateChangeCallback =
+            GetStaticMethodIDOrDie(env, clazz, "invokeFrozenStateChangeCallback",
+                                   "(Landroid/os/IBinder$IFrozenStateChangeCallback;Landroid/os/"
+                                   "IBinder;I)V");
     gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J");
 
     clazz = FindClassOrDie(env, "java/lang/Class");
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/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index fba0d81..7ad18b8 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "NativeLibraryHelper"
 //#define LOG_NDEBUG 0
 
+#include <android-base/properties.h>
 #include <androidfw/ApkParsing.h>
 #include <androidfw/ZipFileRO.h>
 #include <androidfw/ZipUtils.h>
@@ -36,6 +37,7 @@
 #include <zlib.h>
 
 #include <memory>
+#include <string>
 
 #include "com_android_internal_content_FileSystemUtils.h"
 #include "core_jni_helpers.h"
@@ -125,72 +127,10 @@
     return INSTALL_SUCCEEDED;
 }
 
-/*
- * Copy the native library if needed.
- *
- * This function assumes the library and path names passed in are considered safe.
- */
-static install_status_t
-copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName)
-{
-    static const size_t kPageSize = getpagesize();
-    void** args = reinterpret_cast<void**>(arg);
-    jstring* javaNativeLibPath = (jstring*) args[0];
-    jboolean extractNativeLibs = *(jboolean*) args[1];
-    jboolean debuggable = *(jboolean*) args[2];
-
-    ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);
-
-    uint32_t uncompLen;
-    uint32_t when;
-    uint32_t crc;
-
-    uint16_t method;
-    off64_t offset;
-    uint16_t extraFieldLength;
-    if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
-                               &extraFieldLength)) {
-        ALOGE("Couldn't read zip entry info\n");
-        return INSTALL_FAILED_INVALID_APK;
-    }
-
-    // Always extract wrap.sh for debuggable, even if extractNativeLibs=false. This makes it
-    // easier to use wrap.sh because it only works when it is extracted, see
-    // frameworks/base/services/core/java/com/android/server/am/ProcessList.java.
-    bool forceExtractCurrentFile = debuggable && strcmp(fileName, "wrap.sh") == 0;
-
-    if (!extractNativeLibs && !forceExtractCurrentFile) {
-        // check if library is uncompressed and page-aligned
-        if (method != ZipFileRO::kCompressStored) {
-            ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
-                fileName);
-            return INSTALL_FAILED_INVALID_APK;
-        }
-
-        if (offset % kPageSize != 0) {
-            ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
-                  "from apk.\n", fileName, kPageSize);
-            return INSTALL_FAILED_INVALID_APK;
-        }
-
-#ifdef ENABLE_PUNCH_HOLES
-        // if library is uncompressed, punch hole in it in place
-        if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) {
-            ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: "
-                  "%" PRIu64 "",
-                  fileName, zipFile->getZipFileName(), offset);
-        }
-
-        // if extra field for this zip file is present with some length, possibility is that it is
-        // padding added for zip alignment. Punch holes there too.
-        if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
-            ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
-        }
-#endif // ENABLE_PUNCH_HOLES
-
-        return INSTALL_SUCCEEDED;
-    }
-
+static install_status_t extractNativeLibFromApk(ZipFileRO* zipFile, ZipEntryRO zipEntry,
+                                                const char* fileName,
+                                                const std::string nativeLibPath, uint32_t when,
+                                                uint32_t uncompLen, uint32_t crc) {
     // Build local file path
     const size_t fileNameLen = strlen(fileName);
     char localFileName[nativeLibPath.size() + fileNameLen + 2];
@@ -313,6 +253,88 @@
 }
 
 /*
+ * Copy the native library if needed.
+ *
+ * This function assumes the library and path names passed in are considered safe.
+ */
+static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zipFile,
+                                          ZipEntryRO zipEntry, const char* fileName) {
+    static const size_t kPageSize = getpagesize();
+    void** args = reinterpret_cast<void**>(arg);
+    jstring* javaNativeLibPath = (jstring*)args[0];
+    jboolean extractNativeLibs = *(jboolean*)args[1];
+    jboolean debuggable = *(jboolean*)args[2];
+    jboolean app_compat_16kb = *(jboolean*)args[3];
+    install_status_t ret = INSTALL_SUCCEEDED;
+
+    ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);
+
+    uint32_t uncompLen;
+    uint32_t when;
+    uint32_t crc;
+
+    uint16_t method;
+    off64_t offset;
+    uint16_t extraFieldLength;
+    if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
+                               &extraFieldLength)) {
+        ALOGE("Couldn't read zip entry info\n");
+        return INSTALL_FAILED_INVALID_APK;
+    }
+
+    // Always extract wrap.sh for debuggable, even if extractNativeLibs=false. This makes it
+    // easier to use wrap.sh because it only works when it is extracted, see
+    // frameworks/base/services/core/java/com/android/server/am/ProcessList.java.
+    bool forceExtractCurrentFile = debuggable && strcmp(fileName, "wrap.sh") == 0;
+
+    if (!extractNativeLibs && !forceExtractCurrentFile) {
+        // check if library is uncompressed and page-aligned
+        if (method != ZipFileRO::kCompressStored) {
+            ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
+                  fileName);
+            return INSTALL_FAILED_INVALID_APK;
+        }
+
+        if (offset % kPageSize != 0) {
+            // If the library is zip-aligned correctly for 4kb devices and app compat is
+            // enabled, on 16kb devices fallback to extraction
+            if (offset % 0x1000 == 0 && app_compat_16kb) {
+                ALOGI("16kB AppCompat: Library '%s' is not PAGE(%zu)-aligned - falling back to "
+                      "extraction from apk\n",
+                      fileName, kPageSize);
+                return extractNativeLibFromApk(zipFile, zipEntry, fileName, nativeLibPath.c_str(),
+                                               when, uncompLen, crc);
+            }
+
+            ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
+                  "from apk.\n",
+                  fileName, kPageSize);
+            return INSTALL_FAILED_INVALID_APK;
+        }
+
+#ifdef ENABLE_PUNCH_HOLES
+        // if library is uncompressed, punch hole in it in place
+        if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) {
+            ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: "
+                  "%" PRIu64 "",
+                  fileName, zipFile->getZipFileName(), offset);
+        }
+
+        // if extra field for this zip file is present with some length, possibility is that it is
+        // padding added for zip alignment. Punch holes there too.
+        if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
+            ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
+        }
+#endif // ENABLE_PUNCH_HOLES
+
+        return INSTALL_SUCCEEDED;
+    }
+
+    return extractNativeLibFromApk(zipFile, zipEntry, fileName, nativeLibPath.c_str(), when,
+                                   uncompLen, crc);
+}
+
+/*
  * An iterator over all shared libraries in a zip file. An entry is
  * considered to be a shared library if all of the conditions below are
  * satisfied :
@@ -498,12 +520,24 @@
     return status;
 }
 
+static inline bool app_compat_16kb_enabled() {
+    static const size_t kPageSize = getpagesize();
+
+    // App compat is only applicable on 16kb-page-size devices.
+    if (kPageSize != 0x4000) {
+        return false;
+    }
+
+    return android::base::GetBoolProperty("bionic.linker.16kb.app_compat.enabled", false);
+}
+
 static jint
 com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz,
         jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
         jboolean extractNativeLibs, jboolean debuggable)
 {
-    void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable };
+    jboolean app_compat_16kb = app_compat_16kb_enabled();
+    void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable, &app_compat_16kb };
     return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable,
             copyFileIfChanged, reinterpret_cast<void*>(args));
 }
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 020b27e..19f8299 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -391,6 +391,7 @@
 
 } // namespace android
 
+#ifndef _WIN32
 using namespace android;
 
 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
@@ -407,3 +408,4 @@
 
     return JNI_VERSION_1_6;
 }
+#endif
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
index 8de5458..78cf6f4 100644
--- a/core/proto/android/app/appstartinfo.proto
+++ b/core/proto/android/app/appstartinfo.proto
@@ -40,4 +40,5 @@
     optional bytes start_intent = 10;
     optional AppStartLaunchMode launch_mode = 11;
     optional bool was_force_stopped = 12;
+    optional int64 monotonic_creation_time_ms = 13;
 }
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 58f39a9..42c591b 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -1045,7 +1045,7 @@
     repeated Package packages = 2;
 }
 
-// sync with com.android.server.am.am.ProcessList.java
+// LINT.IfChange
 message AppsStartInfoProto {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
@@ -1064,4 +1064,6 @@
         repeated User users = 2;
     }
     repeated Package packages = 2;
+    optional int64 monotonic_time = 3;
 }
+// LINT.ThenChange(/services/core/java/com/android/server/am/AppStartInfoTracker.java)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f795406..5decf7f 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" />
@@ -5605,12 +5603,13 @@
     <!-- This permission is required among systems services to always keep the
          binding with TvInputManagerService.
          <p>This should only be used by the OEM TvInputService.
+         @FlaggedApi("android.media.tv.flags.tif_unbind_inactive_tis")
          <p>Protection level: signature|privileged|vendorPrivileged
          @hide
     -->
     <permission android:name="android.permission.ALWAYS_BOUND_TV_INPUT"
         android:protectionLevel="signature|privileged|vendorPrivileged"
-        android:featureFlag="android.media.tv.flags.tis_always_bound_permission"/>
+        android:featureFlag="android.media.tv.flags.tif_unbind_inactive_tis"/>
 
     <!-- Must be required by a {@link android.media.tv.interactive.TvInteractiveAppService}
          to ensure that only the system can bind to it.
@@ -7649,7 +7648,8 @@
     <permission android:name="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE"
         android:protectionLevel="signature" />
 
-    <!-- Must be required by an {@link android.service.watchdog.ExplicitHealthCheckService} to
+    <!-- @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY) @SystemApi
+         Must be required by an {@link android.service.watchdog.ExplicitHealthCheckService} to
          ensure that only the system can bind to it.
          @hide This is not a third-party API (intended for OEMs and system apps).
     -->
diff --git a/core/res/OWNERS b/core/res/OWNERS
index b2b58d5..5293131 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -78,6 +78,11 @@
 per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS
 per-file res/xml/sms_short_codes.xml = file:/platform/frameworks/opt/telephony:/OWNERS
 
+# Input Method Framework
+per-file res/*/*input_method* = file:/services/core/java/com/android/server/inputmethod/OWNERS
+per-file res/*/*_ime* = file:/services/core/java/com/android/server/inputmethod/OWNERS
+per-file res/*/ime_* = file:/services/core/java/com/android/server/inputmethod/OWNERS
+
 # TV Input Framework
 per-file res/values/config_tv_external_input_logging.xml = file:/services/core/java/com/android/server/tv/OWNERS
 
diff --git a/core/res/res/color/input_method_switch_on_item.xml b/core/res/res/color/input_method_switch_on_item.xml
new file mode 100644
index 0000000..49fe081
--- /dev/null
+++ b/core/res/res/color/input_method_switch_on_item.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_activated="true" android:color="?attr/materialColorOnSecondaryContainer" />
+    <item android:color="?attr/materialColorOnSurface" />
+</selector>
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..e8691fc
--- /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:fillColor="@android:color/white"
+      android:pathData="M480,720Q580,720 650,650Q720,580 720,480Q720,380 650,310Q580,240 480,240L480,480L310,650Q345,683 388.5,701.5Q432,720 480,720ZM480,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>
diff --git a/core/res/res/layout/autofill_dataset_picker_header_footer.xml b/core/res/res/layout/autofill_dataset_picker_header_footer.xml
index 4d5f4f0..027f530 100644
--- a/core/res/res/layout/autofill_dataset_picker_header_footer.xml
+++ b/core/res/res/layout/autofill_dataset_picker_header_footer.xml
@@ -37,6 +37,7 @@
       <ListView
           android:id="@+id/autofill_dataset_list"
           android:layout_weight="1"
+          android:fadeScrollbars="false"
           android:layout_width="fill_parent"
           android:layout_height="0dp"
           android:drawSelectorOnTop="true"
diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml
index 09ed650..10d938c 100644
--- a/core/res/res/layout/input_method_switch_item_new.xml
+++ b/core/res/res/layout/input_method_switch_item_new.xml
@@ -70,6 +70,7 @@
                 android:ellipsize="marquee"
                 android:singleLine="true"
                 android:fontFamily="google-sans-text"
+                android:textColor="@color/input_method_switch_on_item"
                 android:textAppearance="?attr/textAppearanceListItem"/>
 
         </LinearLayout>
@@ -81,7 +82,7 @@
             android:gravity="center_vertical"
             android:layout_marginStart="12dp"
             android:src="@drawable/ic_check_24dp"
-            android:tint="?attr/materialColorOnSurface"
+            android:tint="@color/input_method_switch_on_item"
             android:visibility="gone"
             android:importantForAccessibility="no"/>
 
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..54a3b32 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1183,7 +1183,7 @@
     <string name="copyUrl" msgid="6229645005987260230">"‏کپی URL"</string>
     <string name="selectTextMode" msgid="3225108910999318778">"انتخاب متن"</string>
     <string name="undo" msgid="3175318090002654673">"لغو"</string>
-    <string name="redo" msgid="7231448494008532233">"بازانجام"</string>
+    <string name="redo" msgid="7231448494008532233">"ازنو انجام دادن"</string>
     <string name="autofill" msgid="511224882647795296">"تکمیل خودکار"</string>
     <string name="textSelectionCABTitle" msgid="5151441579532476940">"انتخاب متن"</string>
     <string name="addToDictionary" msgid="8041821113480950096">"افزودن به واژه‌نامه"</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">"فضای ذخیره‌سازی سیستم کافی نیست. اطمینان حاصل کنید که دارای ۲۵۰ مگابایت فضای خالی هستید و سیستم را راه‌اندازی مجدد کنید."</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..6df63d6 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>
@@ -291,8 +291,8 @@
     <string name="notification_channel_security" msgid="8516754650348238057">"સુરક્ષા"</string>
     <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" msgid="1691059964407549150">"ડેવલપર માટેના મેસેજ"</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,28 +379,28 @@
     <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="permdesc_receiveWapPush" msgid="1638677888301778457">"એપ્લિકેશનને 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>
     <string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"પ્રોફાઇલ અને ડિવાઇસ માલિકોને મેનેજ કરો"</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..5593f41 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>
@@ -1722,7 +1721,7 @@
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Verwijderen"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Volume verhogen tot boven het aanbevolen niveau?\n\nAls je langere tijd op hoog volume naar muziek luistert, raakt je gehoor mogelijk beschadigd."</string>
-    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"Wil je blijven luisteren op hoog volume?\n\nHet hoofdtelefoonvolume is langer dan de aanbevolen tijd hoog geweest. Dit kan je gehoor beschadigen."</string>
+    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"Wil je blijven luisteren op hoog volume?\n\nHet koptelefoonvolume is langer dan de aanbevolen tijd hoog geweest. Dit kan je gehoor beschadigen."</string>
     <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"Hard geluid gedetecteerd\n\nHet hoofdtelefoonvolume is hoger dan aanbevolen. Dit kan je gehoor beschadigen."</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Snelkoppeling toegankelijkheid gebruiken?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Als de snelkoppeling aanstaat, houd je beide volumeknoppen 3 seconden ingedrukt om een toegankelijkheidsfunctie te starten."</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..805dce5 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>
@@ -1722,7 +1721,7 @@
     <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
     <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Alisin"</string>
     <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lakasan ang volume nang lagpas sa inirerekomendang antas?\n\nMaaaring mapinsala ng pakikinig sa malakas na volume sa loob ng mahahabang panahon ang iyong pandinig."</string>
-    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"Magpatuloy sa pakikinig nang may malakas na volume?\n\nNaging malakas ang volume nang mas matagal na sa inirerekomenda, at posible nitong mapinsala ang pandinig mo"</string>
+    <string name="csd_dose_reached_warning" product="default" msgid="491875107583931974">"Magpatuloy sa pakikinig nang may malakas na volume?\n\nMalakas ang volume nang mas matagal na sa inirerekomenda, at posible nitong mapinsala ang pandinig mo"</string>
     <string name="csd_momentary_exposure_warning" product="default" msgid="7730840903435405501">"Naka-detect ng malakas na tunog\n\nMas malakas ang volume kaysa sa inirerekomenda, at posible nitong mapinsala ang pandinig mo"</string>
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gagamitin ang Shortcut sa Accessibility?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kapag naka-on ang shortcut, magsisimula ang isang feature ng pagiging naa-access kapag pinindot ang parehong button ng volume."</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.xml b/core/res/res/values/config.xml
index de7477e..4beeb17 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3080,6 +3080,11 @@
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
+    <!-- Whether to boot system with the headless system user, i.e. user 0. If set to true,
+         system will be booted with the headless system user, or user 0. It has no effect if device
+         is not in Headless System User Mode (HSUM). -->
+    <bool name="config_bootToHeadlessSystemUser">false</bool>
+
     <!-- Whether multiple admins are allowed on the device. If set to true, new users can be created
          with admin privileges and admin privileges can be granted/revoked from existing users. -->
     <bool name="config_enableMultipleAdmins">false</bool>
@@ -7025,6 +7030,10 @@
          {@link InputDevice#SOURCE_ROTARY_ENCODER}s. -->
     <bool name="config_viewBasedRotaryEncoderHapticsEnabled">false</bool>
 
+    <!-- Whether the scroll haptic feedback implementation is enabled for
+    {@link InputDevice#SOURCE_TOUCHSCREEN}s. -->
+    <bool name="config_viewTouchScreenHapticScrollFeedbackEnabled">false</bool>
+
     <!-- Whether the media player is shown on the quick settings -->
     <bool name="config_quickSettingsShowMediaPlayer">true</bool>
 
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 118acac..69437b4 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -440,7 +440,19 @@
     <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>
+    <java-symbol type="integer" name="config_satellite_delay_minutes_before_retry_validating_possible_change_in_allowed_region" />
+
+    <!-- The maximum retry count to validate a possible change in satellite allowed region.
+         The default value is 3 minutes. -->
+    <integer name="config_satellite_max_retry_count_for_validating_possible_change_in_allowed_region">3</integer>
+    <java-symbol type="integer" name="config_satellite_max_retry_count_for_validating_possible_change_in_allowed_region" />
+
+    <!-- The time duration in minutes for location query throttle interval.
+         The default value is 10 minutes. -->
+    <integer name="config_satellite_location_query_throttle_interval_minutes">10</integer>
+    <java-symbol type="integer" name="config_satellite_location_query_throttle_interval_minutes" />
+
 </resources>
diff --git a/core/res/res/values/config_tv_external_input_logging.xml b/core/res/res/values/config_tv_external_input_logging.xml
index 72e30be..293a183 100644
--- a/core/res/res/values/config_tv_external_input_logging.xml
+++ b/core/res/res/values/config_tv_external_input_logging.xml
@@ -24,27 +24,39 @@
      entries do not follow the convention, but all new entries should. -->
 
 <resources>
-    <bool name="config_tvExternalInputLoggingDisplayNameFilterEnabled">false</bool>
+    <bool name="config_tvExternalInputLoggingDisplayNameFilterEnabled">true</bool>
 
     <string-array name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames">
-        <item>Chromecast</item>
+        <item>ADT-4</item>
         <item>Chromecast HD</item>
-        <item>SHIELD</item>
-        <item>Roku</item>
-        <item>Roku Express 4</item>
-        <item>Home Theater</item>
         <item>Fire TV Stick</item>
-        <item>PlayStation 5</item>
+        <item>Freebox Player</item>
+        <item>Home Theater</item>
+        <item>Jarvis</item>
         <item>NintendoSwitch</item>
+        <item>onn. 4K Plus S</item>
+        <item>onn. Streaming</item>
+        <item>PlayStation 4</item>
+        <item>PlayStation 5</item>
+        <item>Roku 3</item>
+        <item>Roku Express 4</item>
     </string-array>
 
     <string-array name="config_tvExternalInputLoggingDeviceBrandNames">
-        <item>Chromecast</item>
-        <item>SHIELD</item>
-        <item>Roku</item>
         <item>Apple</item>
+        <item>Chromecast</item>
         <item>Fire TV</item>
-        <item>PlayStation</item>
+        <item>Freebox</item>
+        <item>Google</item>
+        <item>MiBOX</item>
+        <item>Microsoft</item>
         <item>Nintendo</item>
+        <item>NVIDIA</item>
+        <item>onn.</item>
+        <item>PlayStation</item>
+        <item>Roku</item>
+        <item>SHIELD</item>
+        <item>Sony</item>
+        <item>XBOX</item>
     </string-array>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f404666..606c7fd 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4804,9 +4804,9 @@
     </string>
 
     <!-- Title for accessibility edit shortcut selection menu dialog, and dialog is triggered
-    from volume key shortcut. [CHAR LIMIT=100] -->
+    from volume keys shortcut. [CHAR LIMIT=100] -->
     <string name="accessibility_edit_shortcut_menu_volume_title">Choose features to use with the
-        volume key shortcut
+        volume keys shortcut
     </string>
 
     <!-- Text for showing the warning to user when uncheck the legacy app item in the accessibility
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 0d16e9c..80ec67a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -363,6 +363,7 @@
   <java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/>
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
   <java-symbol type="bool" name="config_enableMultipleAdmins"/>
+  <java-symbol type="bool" name="config_bootToHeadlessSystemUser"/>
   <java-symbol type="bool" name="config_omnipresentCommunalUser"/>
   <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
   <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
@@ -5465,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" />
@@ -5487,8 +5490,11 @@
   <java-symbol type="bool" name="config_tvExternalInputLoggingDisplayNameFilterEnabled" />
   <java-symbol type="array" name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames" />
   <java-symbol type="array" name="config_tvExternalInputLoggingDeviceBrandNames" />
+
+  <!-- Scroll Feedback Configs -->
   <java-symbol type="bool" name="config_viewRotaryEncoderHapticScrollFedbackEnabled" />
   <java-symbol type="bool" name="config_viewBasedRotaryEncoderHapticsEnabled" />
+  <java-symbol type="bool" name="config_viewTouchScreenHapticScrollFeedbackEnabled" />
 
   <java-symbol type="bool" name="config_quickSettingsShowMediaPlayer" />
 
@@ -5563,6 +5569,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" />
@@ -5572,6 +5579,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/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 452ae04..d35bfb7 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -131,7 +131,7 @@
         <item name="progressBarStyleSmallInverse">@style/Widget.DeviceDefault.ProgressBar.Small.Inverse</item>
         <item name="progressBarStyleLargeInverse">@style/Widget.DeviceDefault.ProgressBar.Large.Inverse</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="seekBarStyle">@style/Widget.DeviceDefault.SeekBar</item>
         <item name="ratingBarStyle">@style/Widget.DeviceDefault.RatingBar</item>
         <item name="ratingBarStyleIndicator">@style/Widget.DeviceDefault.RatingBar.Indicator</item>
@@ -351,7 +351,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -468,7 +468,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -587,7 +587,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -705,7 +705,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -831,7 +831,7 @@
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -948,7 +948,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -1064,7 +1064,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -1181,7 +1181,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -1314,7 +1314,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -1432,7 +1432,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -1548,7 +1548,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -1666,7 +1666,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -1783,7 +1783,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -1900,7 +1900,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -2017,7 +2017,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -2134,7 +2134,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -2251,7 +2251,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -2373,7 +2373,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -2488,7 +2488,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -2642,7 +2642,7 @@
         <item name="progressBarStyleSmallInverse">@style/Widget.DeviceDefault.Light.ProgressBar.Small.Inverse</item>
         <item name="progressBarStyleLargeInverse">@style/Widget.DeviceDefault.Light.ProgressBar.Large.Inverse</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="seekBarStyle">@style/Widget.DeviceDefault.Light.SeekBar</item>
         <item name="ratingBarStyle">@style/Widget.DeviceDefault.Light.RatingBar</item>
         <item name="ratingBarStyleIndicator">@style/Widget.DeviceDefault.Light.RatingBar.Indicator</item>
@@ -2858,7 +2858,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -2974,7 +2974,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -3091,7 +3091,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -3210,7 +3210,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -3328,7 +3328,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -3452,7 +3452,7 @@
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -3572,7 +3572,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -3691,7 +3691,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -3811,7 +3811,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -4133,7 +4133,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -4254,7 +4254,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -4373,7 +4373,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -4491,7 +4491,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -4608,7 +4608,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -4725,7 +4725,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -4840,7 +4840,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -5066,7 +5066,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -5162,7 +5162,7 @@
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -5280,7 +5280,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -5471,7 +5471,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -5522,7 +5522,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
@@ -5641,7 +5641,7 @@
         <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
 
         <!-- Progress bar attributes -->
-        <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+        <item name="colorProgressBackgroundNormal">?attr/materialColorOutline</item>
         <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
 
         <!-- Toolbar attributes -->
diff --git a/packages/SystemUI/res/color/brightness_slider_track.xml b/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml
similarity index 74%
rename from packages/SystemUI/res/color/brightness_slider_track.xml
rename to core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml
index 6028769..7ac0787 100644
--- a/packages/SystemUI/res/color/brightness_slider_track.xml
+++ b/core/res/res/xml/haptic_feedback_customization_source_rotary_encoder.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?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");
@@ -14,6 +15,4 @@
   ~ limitations under the License.
   -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral2_500" android:lStar="40" />
-</selector>
\ No newline at end of file
+<haptic-feedback-constants/>
diff --git a/packages/SystemUI/res/color/brightness_slider_track.xml b/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml
similarity index 74%
copy from packages/SystemUI/res/color/brightness_slider_track.xml
copy to core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml
index 6028769..7ac0787 100644
--- a/packages/SystemUI/res/color/brightness_slider_track.xml
+++ b/core/res/res/xml/haptic_feedback_customization_source_touchscreen.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?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");
@@ -14,6 +15,4 @@
   ~ limitations under the License.
   -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral2_500" android:lStar="40" />
-</selector>
\ No newline at end of file
+<haptic-feedback-constants/>
diff --git a/core/tests/GameManagerTests/Android.bp b/core/tests/GameManagerTests/Android.bp
index 0e3bc65..a252f8b 100644
--- a/core/tests/GameManagerTests/Android.bp
+++ b/core/tests/GameManagerTests/Android.bp
@@ -37,3 +37,10 @@
     certificate: "platform",
     test_suites: ["device-tests"],
 }
+
+test_module_config {
+    name: "FrameworksCoreGameManagerTests_android_app",
+    base: "FrameworksCoreGameManagerTests",
+    test_suites: ["device-tests"],
+    include_filters: ["android.app"],
+}
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/packages/SystemUI/res/color/brightness_slider_track.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/strings.xml
similarity index 68%
copy from packages/SystemUI/res/color/brightness_slider_track.xml
copy to core/tests/batterystatstests/BatteryStatsViewer/res/values/strings.xml
index 6028769..c23c148 100644
--- a/packages/SystemUI/res/color/brightness_slider_track.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/strings.xml
@@ -1,4 +1,4 @@
-<?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");
@@ -14,6 +14,7 @@
   ~ limitations under the License.
   -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral2_500" android:lStar="40" />
-</selector>
\ No newline at end of file
+<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..99cbf05 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -21,6 +21,7 @@
     srcs: [
         "DisabledTestApp/src/**/*.java",
         "EnabledTestApp/src/**/*.java",
+        "BinderFrozenStateChangeCallbackTestApp/src/**/*.java",
         "BinderProxyCountingTestApp/src/**/*.java",
         "BinderProxyCountingTestService/src/**/*.java",
         "BinderDeathRecipientHelperApp/src/**/*.java",
@@ -138,6 +139,7 @@
         ":BinderDeathRecipientHelperApp1",
         ":BinderDeathRecipientHelperApp2",
         ":com.android.cts.helpers.aosp",
+        ":BinderFrozenStateChangeCallbackTestApp",
         ":BinderProxyCountingTestApp",
         ":BinderProxyCountingTestService",
         ":AppThatUsesAppOps",
@@ -803,3 +805,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/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 99b73a4..b1f1e2c 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -22,6 +22,7 @@
         <option name="test-file-name" value="FrameworksCoreTests.apk" />
         <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" />
         <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
+        <option name="test-file-name" value="BinderFrozenStateChangeCallbackTestApp.apk" />
         <option name="test-file-name" value="BinderProxyCountingTestApp.apk" />
         <option name="test-file-name" value="BinderProxyCountingTestService.apk" />
         <option name="test-file-name" value="AppThatUsesAppOps.apk" />
diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/Android.bp b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/Android.bp
new file mode 100644
index 0000000..de97dda
--- /dev/null
+++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+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"],
+}
+
+android_test_helper_app {
+    name: "BinderFrozenStateChangeCallbackTestApp",
+
+    static_libs: ["coretests-aidl"],
+    srcs: ["**/*.java"],
+
+    platform_apis: true,
+    certificate: "platform",
+
+    test_suites: ["device-tests"],
+}
diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/AndroidManifest.xml b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..29c8f55
--- /dev/null
+++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.bfscctestapp">
+
+    <application>
+        <service android:name=".BfsccTestAppCmdService"
+                 android:exported="true"/>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
new file mode 100644
index 0000000..77e8a40
--- /dev/null
+++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
@@ -0,0 +1,71 @@
+/*
+ * 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.coretests.bfscctestapp;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.frameworks.coretests.aidl.IBfsccTestAppCmdService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class BfsccTestAppCmdService extends Service {
+    private IBfsccTestAppCmdService.Stub mBinder = new IBfsccTestAppCmdService.Stub() {
+        private final LinkedBlockingQueue<IBinder.IFrozenStateChangeCallback.State> mNotifications =
+                new LinkedBlockingQueue<>();
+
+        @Override
+        public void listenTo(IBinder binder) throws RemoteException {
+            binder.addFrozenStateChangeCallback(
+                    (IBinder who, IBinder.IFrozenStateChangeCallback.State state)
+                            -> mNotifications.offer(state));
+        }
+
+        @Override
+        public boolean[] waitAndConsumeNotifications() {
+            List<Boolean> results = new ArrayList<>();
+            try {
+                IBinder.IFrozenStateChangeCallback.State state =
+                        mNotifications.poll(5, TimeUnit.SECONDS);
+                if (state != null) {
+                    results.add(state == IBinder.IFrozenStateChangeCallback.State.FROZEN);
+                }
+            } catch (InterruptedException e) {
+                return null;
+            }
+            while (mNotifications.size() > 0) {
+                results.add(mNotifications.poll()
+                        == IBinder.IFrozenStateChangeCallback.State.FROZEN);
+            }
+            boolean[] convertedResults = new boolean[results.size()];
+            for (int i = 0; i < results.size(); i++) {
+                convertedResults[i] = results.get(i).booleanValue();
+            }
+            return convertedResults;
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java
index 41b4c69..09d79a6 100644
--- a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java
+++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java
@@ -50,4 +50,4 @@
     public IBinder onBind(Intent intent) {
         return mBinder;
     }
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBfsccTestAppCmdService.aidl
similarity index 78%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
copy to core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBfsccTestAppCmdService.aidl
index 3c5beeb..d8d7dc4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBfsccTestAppCmdService.aidl
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.frameworks.coretests.aidl;
 
-parcelable BubbleBarLocation;
\ No newline at end of file
+interface IBfsccTestAppCmdService {
+   void listenTo(IBinder binder);
+   boolean[] waitAndConsumeNotifications();
+}
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index c08e42b..e47ef2d 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -233,6 +233,33 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_NOTIFICATION_CHANNEL_VIBRATION_EFFECT_API,
+            Flags.FLAG_NOTIF_CHANNEL_CROP_VIBRATION_EFFECTS})
+    public void testLongVibrationFields_canWriteToXml() throws Exception {
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        // populate pattern with contents
+        long[] pattern = new long[65550 / 2];
+        for (int i = 0; i < pattern.length; i++) {
+            pattern[i] = 100;
+        }
+        channel.setVibrationPattern(pattern);  // with flag on, also sets effect
+
+        // Send it through parceling & unparceling to simulate being passed through a binder call
+        NotificationChannel fromParcel = writeToAndReadFromParcel(channel);
+        assertThat(fromParcel.getVibrationPattern().length).isEqualTo(
+                NotificationChannel.MAX_VIBRATION_LENGTH);
+
+        // Confirm that this also survives writing to & restoring from XML
+        NotificationChannel result = backUpAndRestore(fromParcel);
+        assertThat(result.getVibrationPattern().length).isEqualTo(
+                NotificationChannel.MAX_VIBRATION_LENGTH);
+        assertThat(result.getVibrationEffect()).isNotNull();
+        assertThat(result.getVibrationEffect()
+                .computeCreateWaveformOffOnTimingsOrNull())
+                .isEqualTo(result.getVibrationPattern());
+    }
+
+    @Test
     public void testRestoreSoundUri_customLookup() throws Exception {
         Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
         Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
diff --git a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
new file mode 100644
index 0000000..ee2e7e0
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
+
+import com.android.frameworks.coretests.aidl.IBfsccTestAppCmdService;
+import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Queue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests functionality of {@link android.os.IBinder.IFrozenStateChangeCallback}.
+ */
+@RunWith(AndroidJUnit4.class)
+@IgnoreUnderRavenwood(blockedBy = ActivityManager.class)
+public class BinderFrozenStateChangeNotificationTest {
+    private static final String TAG = BinderFrozenStateChangeNotificationTest.class.getSimpleName();
+
+    private static final String TEST_PACKAGE_NAME_1 =
+            "com.android.frameworks.coretests.bfscctestapp";
+    private static final String TEST_PACKAGE_NAME_2 =
+            "com.android.frameworks.coretests.bdr_helper_app1";
+    private static final String TEST_APP_CMD_SERVICE =
+            TEST_PACKAGE_NAME_1 + ".BfsccTestAppCmdService";
+
+    private static final int CALLBACK_WAIT_TIMEOUT_SECS = 5;
+
+    private IBfsccTestAppCmdService mBfsccTestAppCmdService;
+    private ServiceConnection mTestAppConnection;
+    private Context mContext;
+    private Handler mHandler;
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mHandler = new Handler(Looper.getMainLooper());
+        ((ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE)).killUid(
+                mContext.getPackageManager().getPackageUid(TEST_PACKAGE_NAME_1, 0),
+                "Wiping Test Package");
+        mTestAppConnection = bindService();
+    }
+
+    private IBinder getNewRemoteBinder(String testPackage) throws InterruptedException {
+        final CountDownLatch resultLatch = new CountDownLatch(1);
+        final AtomicInteger resultCode = new AtomicInteger(Activity.RESULT_CANCELED);
+        final AtomicReference<Bundle> resultExtras = new AtomicReference<>();
+
+        final Intent intent = new Intent(TestCommsReceiver.ACTION_GET_BINDER)
+                .setClassName(testPackage, TestCommsReceiver.class.getName());
+        mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                resultCode.set(getResultCode());
+                resultExtras.set(getResultExtras(true));
+                resultLatch.countDown();
+            }
+        }, mHandler, Activity.RESULT_CANCELED, null, null);
+
+        assertTrue("Request for binder timed out", resultLatch.await(5, TimeUnit.SECONDS));
+        assertEquals(Activity.RESULT_OK, resultCode.get());
+        return resultExtras.get().getBinder(TestCommsReceiver.EXTRA_KEY_BINDER);
+    }
+
+    private ServiceConnection bindService()
+            throws Exception {
+        final CountDownLatch bindLatch = new CountDownLatch(1);
+        ServiceConnection connection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                Log.i(TAG, "Service connected");
+                mBfsccTestAppCmdService = IBfsccTestAppCmdService.Stub.asInterface(service);
+                bindLatch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                Log.i(TAG, "Service disconnected");
+            }
+        };
+        mContext.bindService(
+                new Intent().setComponent(
+                        new ComponentName(TEST_PACKAGE_NAME_1, TEST_APP_CMD_SERVICE)),
+                connection,
+                Context.BIND_AUTO_CREATE
+                        | Context.BIND_NOT_FOREGROUND);
+        if (!bindLatch.await(5, TimeUnit.SECONDS)) {
+            fail("Timed out waiting for the service to bind");
+        }
+        return connection;
+    }
+
+    private void unbindService(ServiceConnection service) {
+        if (service != null) {
+            mContext.unbindService(service);
+        }
+    }
+
+    @Test
+    public void onStateChangeCalled() throws Exception {
+        final LinkedBlockingQueue<Boolean> results = new LinkedBlockingQueue<>();
+        if (createCallback(mBfsccTestAppCmdService.asBinder(), results) == null) {
+            return;
+        }
+        ensureUnfrozenCallback(results);
+        freezeApp1();
+        ensureFrozenCallback(results);
+        unfreezeApp1();
+        ensureUnfrozenCallback(results);
+    }
+
+    @Test
+    public void onStateChangeNotCalledAfterCallbackRemoved() throws Exception {
+        final LinkedBlockingQueue<Boolean> results = new LinkedBlockingQueue<>();
+        IBinder.IFrozenStateChangeCallback callback;
+        if ((callback = createCallback(mBfsccTestAppCmdService.asBinder(), results)) == null) {
+            return;
+        }
+        ensureUnfrozenCallback(results);
+        mBfsccTestAppCmdService.asBinder().removeFrozenStateChangeCallback(callback);
+        freezeApp1();
+        assertEquals("No more callbacks should be invoked.", 0, results.size());
+    }
+
+    @Test
+    public void multipleCallbacks() throws Exception {
+        final LinkedBlockingQueue<Boolean> results1 = new LinkedBlockingQueue<>();
+        final LinkedBlockingQueue<Boolean> results2 = new LinkedBlockingQueue<>();
+        IBinder.IFrozenStateChangeCallback callback1;
+        if ((callback1 = createCallback(mBfsccTestAppCmdService.asBinder(), results1)) == null) {
+            return;
+        }
+        ensureUnfrozenCallback(results1);
+        freezeApp1();
+        ensureFrozenCallback(results1);
+        if (createCallback(mBfsccTestAppCmdService.asBinder(), results2) == null) {
+            return;
+        }
+        ensureFrozenCallback(results2);
+
+        unfreezeApp1();
+        ensureUnfrozenCallback(results1);
+        ensureUnfrozenCallback(results2);
+
+        mBfsccTestAppCmdService.asBinder().removeFrozenStateChangeCallback(callback1);
+        freezeApp1();
+        assertEquals("No more callbacks should be invoked.", 0, results1.size());
+        ensureFrozenCallback(results2);
+    }
+
+    @Test
+    public void onStateChangeCalledWithTheRightBinder() throws Exception {
+        final IBinder binder = mBfsccTestAppCmdService.asBinder();
+        final LinkedBlockingQueue<IBinder> results = new LinkedBlockingQueue<>();
+        IBinder.IFrozenStateChangeCallback callback =
+                (IBinder who, IBinder.IFrozenStateChangeCallback.State state) -> results.offer(who);
+        try {
+            binder.addFrozenStateChangeCallback(callback);
+        } catch (UnsupportedOperationException e) {
+            return;
+        }
+        assertEquals("Callback received the wrong Binder object.",
+                binder, results.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS));
+        freezeApp1();
+        assertEquals("Callback received the wrong Binder object.",
+                binder, results.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS));
+        unfreezeApp1();
+        assertEquals("Callback received the wrong Binder object.",
+                binder, results.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS));
+    }
+
+    @After
+    public void tearDown() {
+        if (mTestAppConnection != null) {
+            mContext.unbindService(mTestAppConnection);
+        }
+    }
+
+    private IBinder.IFrozenStateChangeCallback createCallback(IBinder binder, Queue<Boolean> queue)
+            throws RemoteException {
+        try {
+            final IBinder.IFrozenStateChangeCallback callback =
+                    (IBinder who, IBinder.IFrozenStateChangeCallback.State state) ->
+                            queue.offer(state == IBinder.IFrozenStateChangeCallback.State.FROZEN);
+            binder.addFrozenStateChangeCallback(callback);
+            return callback;
+        } catch (UnsupportedOperationException e) {
+            return null;
+        }
+    }
+
+    private void ensureFrozenCallback(LinkedBlockingQueue<Boolean> queue)
+            throws InterruptedException {
+        assertEquals(Boolean.TRUE, queue.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS));
+    }
+
+    private void ensureUnfrozenCallback(LinkedBlockingQueue<Boolean> queue)
+            throws InterruptedException {
+        assertEquals(Boolean.FALSE, queue.poll(CALLBACK_WAIT_TIMEOUT_SECS, TimeUnit.SECONDS));
+    }
+
+    private String executeShellCommand(String cmd) throws Exception {
+        return UiDevice.getInstance(
+                InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
+    }
+
+    private void freezeApp1() throws Exception {
+        executeShellCommand("am freeze " + TEST_PACKAGE_NAME_1);
+    }
+
+    private void freezeApp2() throws Exception {
+        executeShellCommand("am freeze " + TEST_PACKAGE_NAME_2);
+    }
+
+    private void unfreezeApp1() throws Exception {
+        executeShellCommand("am unfreeze " + TEST_PACKAGE_NAME_1);
+    }
+
+    private void unfreezeApp2() throws Exception {
+        executeShellCommand("am unfreeze " + TEST_PACKAGE_NAME_2);
+    }
+}
diff --git a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
index ecd2f76..b157c95 100644
--- a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
@@ -16,8 +16,11 @@
 
 package android.os.storage;
 
+import android.content.res.ObbInfo;
+import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.ProxyFileDescriptorCallback;
+import android.os.ServiceManager;
 import android.system.ErrnoException;
 
 import androidx.test.filters.LargeTest;
@@ -104,7 +107,14 @@
     public void testMountBadPackageNameObb() throws Exception {
         final File file = createObbFile(OBB_FILE_3_BAD_PACKAGENAME, R.raw.obb_file3_bad_packagename);
         String filePath = file.getAbsolutePath();
-        mountObb(filePath, OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
+        try {
+            mountObb(filePath, OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
+            fail("mountObb should throw an exception as package name is incorrect");
+        } catch (Exception ex) {
+            assertEquals("Path " + filePath
+                            + " does not contain package name " + mContext.getPackageName(),
+                    ex.getMessage());
+        }
     }
 
     /**
@@ -154,6 +164,48 @@
         }
     }
 
+    @LargeTest
+    public void testObbInfo_withValidObbInfo_success() throws Exception {
+        final File file = createObbFile(OBB_FILE_1, R.raw.obb_file1);
+        String filePath = file.getAbsolutePath();
+        try {
+            mountObb(filePath);
+            unmountObb(filePath, DONT_FORCE);
+        } catch (Exception ex) {
+            fail("No exception expected, got " + ex.getMessage());
+        }
+    }
+
+    @LargeTest
+    public void testObbInfo_withInvalidObbInfo_exception() throws Exception {
+        final File file = createObbFile(OBB_FILE_1, R.raw.obb_file1);
+        String rawPath = file.getAbsolutePath();
+        String canonicalPath = file.getCanonicalPath();
+
+        ObbInfo obbInfo = ObbInfo.CREATOR.createFromParcel(Parcel.obtain());
+        obbInfo.packageName = "com.android.obbcrash";
+        obbInfo.version = 1;
+        obbInfo.filename = canonicalPath;
+
+        try {
+            IStorageManager.Stub.asInterface(ServiceManager.getServiceOrThrow("mount")).mountObb(
+                    rawPath, canonicalPath, new ObbActionListener(), 0, obbInfo);
+            fail("mountObb should throw an exception as package name is incorrect");
+        } catch (SecurityException ex) {
+            assertEquals("Path " + canonicalPath
+                            + " does not contain package name " + mContext.getPackageName(),
+                    ex.getMessage());
+        }
+    }
+
+    private static class ObbActionListener extends IObbActionListener.Stub {
+        @SuppressWarnings("hiding")
+        @Override
+        public void onObbResult(String filename, int nonce, int status) {
+
+        }
+    }
+
     private static class MyThreadFactory implements ThreadFactory {
         Thread thread = null;
 
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/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
index 4d9b591c..00ffda8 100644
--- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
+++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java
@@ -254,11 +254,8 @@
         float progress = 0.5f;
         mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, progress, EDGE_LEFT));
         // verify correct ime insets manipulation
-        float interpolatedProgress = BACK_GESTURE.getInterpolation(progress);
-        int expectedInset =
-                (int) (IME_HEIGHT - interpolatedProgress * PEEK_FRACTION * IME_HEIGHT);
         verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha(
-                eq(Insets.of(0, 0, 0, expectedInset)), eq(1f), anyFloat());
+                eq(Insets.of(0, 0, 0, getImeHeight(progress))), eq(1f), anyFloat());
     }
 
     @Test
@@ -268,12 +265,13 @@
             WindowInsetsAnimationControlListener animationControlListener = startBackGesture();
 
             // progress back gesture
-            mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT));
+            float progress = 0.5f;
+            mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, progress, EDGE_LEFT));
 
             // commit back gesture
             mBackAnimationController.onBackInvoked();
 
-            // verify setInsetsAndAlpha never called due onReady delayed
+            // verify setInsetsAndAlpha never called due to onReady delayed
             verify(mWindowInsetsAnimationController, never()).setInsetsAndAlpha(any(), anyInt(),
                     anyFloat());
             verify(mInsetsController, never()).setPredictiveBackImeHideAnimInProgress(eq(true));
@@ -283,7 +281,7 @@
 
             // verify setInsetsAndAlpha immediately called
             verify(mWindowInsetsAnimationController, times(1)).setInsetsAndAlpha(
-                    eq(Insets.of(0, 0, 0, IME_HEIGHT)), eq(1f), anyFloat());
+                    eq(Insets.of(0, 0, 0, getImeHeight(progress))), eq(1f), anyFloat());
             // verify post-commit hide anim has started
             verify(mInsetsController, times(1)).setPredictiveBackImeHideAnimInProgress(eq(true));
         });
@@ -319,4 +317,9 @@
 
         return animationControlListener.getValue();
     }
+
+    private int getImeHeight(float gestureProgress) {
+        float interpolatedProgress = BACK_GESTURE.getInterpolation(gestureProgress);
+        return (int) (IME_HEIGHT - interpolatedProgress * PEEK_FRACTION * IME_HEIGHT);
+    }
 }
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/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 7bc0d2f..bec8b1f 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -69,6 +69,7 @@
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.LinearInterpolator;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 import android.widget.TextView;
 
@@ -136,7 +137,7 @@
             mTestHandler = new TestHandler(null, mTestClock);
             mTestHost = spy(new TestHost(mViewRoot));
             mController = new InsetsController(mTestHost, (controller, id, type) -> {
-                if (type == ime()) {
+                if (!Flags.refactorInsetsController() && type == ime()) {
                     return new InsetsSourceConsumer(id, type, controller.getState(),
                             Transaction::new, controller) {
 
@@ -260,7 +261,11 @@
             mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             // When using the animation thread, this must not invoke onReady()
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
         });
@@ -277,7 +282,12 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                InsetsSourceControl ime = createControl(ID_IME, ime());
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
             mController.show(all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimations();
@@ -299,7 +309,11 @@
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(ID_IME, ime()).onWindowFocusGained(true);
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             mController.cancelExistingAnimations();
             assertTrue(isRequestedVisible(mController, ime()));
             mController.hide(ime(), true /* fromIme */, ImeTracker.Token.empty());
@@ -469,7 +483,12 @@
             assertFalse(mController.getState().peekSource(ID_IME).isVisible());
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                InsetsSourceControl ime = createControl(ID_IME, ime());
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
 
             // Gaining control shortly after
             mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
@@ -493,7 +512,12 @@
             mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                InsetsSourceControl ime = createControl(ID_IME, ime());
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
 
             assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
@@ -558,7 +582,13 @@
 
     @Test
     public void testControlImeNotReady() {
-        prepareControls();
+        if (!Flags.refactorInsetsController()) {
+            prepareControls();
+        } else {
+            // With the flag on, the IME control should not contain a leash, otherwise the custom
+            // animation will start immediately.
+            prepareControls(false /* imeControlHasLeash */);
+        }
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
@@ -571,7 +601,13 @@
             verify(listener, never()).onReady(any(), anyInt());
 
             // Pretend that IME is calling.
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                // Send the IME control with leash, so that the animation can start
+                InsetsSourceControl ime = createControl(ID_IME, ime(), true /* hasLeash */);
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
 
             // Ready gets deferred until next predraw
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
@@ -583,7 +619,13 @@
 
     @Test
     public void testControlImeNotReady_controlRevoked() {
-        prepareControls();
+        if (!Flags.refactorInsetsController()) {
+            prepareControls();
+        } else {
+            // With the flag on, the IME control should not contain a leash, otherwise the custom
+            // animation will start immediately.
+            prepareControls(false /* imeControlHasLeash */);
+        }
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
@@ -604,7 +646,13 @@
 
     @Test
     public void testControlImeNotReady_timeout() {
-        prepareControls();
+        if (!Flags.refactorInsetsController()) {
+            prepareControls();
+        } else {
+            // With the flag on, the IME control should not contain a leash, otherwise the custom
+            // animation will start immediately.
+            prepareControls(false /* imeControlHasLeash */);
+        }
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener listener =
                     mock(WindowInsetsAnimationControlListener.class);
@@ -655,7 +703,11 @@
             mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
 
             // Pretend IME is calling
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
 
             InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
             copy.peekSource(ID_IME).setFrame(0, 1, 2, 3);
@@ -886,7 +938,11 @@
 
             // Showing invisible ime should only causes insets change once.
             clearInvocations(mTestHost);
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             verify(mTestHost, times(1)).notifyInsetsChanged();
 
             // Sending the same insets state should not cause insets change.
@@ -953,7 +1009,11 @@
             assertNull(imeInsetsConsumer.getControl());
 
             // Verify IME requested visibility should be updated to IME consumer from controller.
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             assertTrue(isRequestedVisible(mController, ime()));
 
             mController.hide(ime());
@@ -962,11 +1022,15 @@
     }
 
     @Test
-    public void testImeRequestedVisibleDuringPredictiveBackAnim() {
+    public void testImeRequestedVisibleDuringPredictiveBackAnimWithoutCallback() {
         prepareControls();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // show ime as initial state
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             mController.cancelExistingAnimations(); // fast forward show animation
             assertTrue(mController.getState().peekSource(ID_IME).isVisible());
 
@@ -987,11 +1051,52 @@
     }
 
     @Test
+    public void testImeRequestedInvisibleDuringPredictiveBackAnimWithCallback() {
+        prepareControls();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // set WindowInsetsAnimationCallback on ViewRoot
+            mViewRoot.getView().setWindowInsetsAnimationCallback(
+                    new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+                        @Override
+                        public WindowInsets onProgress(
+                                @NonNull WindowInsets insets,
+                                @NonNull List<WindowInsetsAnimation> runningAnimations) {
+                            return insets;
+                        }
+                    });
+
+            // show ime as initial state
+            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            mController.cancelExistingAnimations(); // fast forward show animation
+            assertTrue(mController.getState().peekSource(ID_IME).isVisible());
+
+            // start control request (for predictive back animation)
+            WindowInsetsAnimationControlListener listener =
+                    mock(WindowInsetsAnimationControlListener.class);
+            mController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null,
+                    listener, /*fromIme*/ false, /*duration*/ -1, /*interpolator*/ null,
+                    ANIMATION_TYPE_USER, /*fromPredictiveBack*/ true);
+
+            // Verify that onReady is called (after next predraw)
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+            verify(listener).onReady(notNull(), eq(ime()));
+
+            // verify that insets are requested invisible during animation
+            assertFalse(isRequestedVisible(mController, ime()));
+        });
+    }
+
+    @Test
     public void testImeShowRequestCancelsPredictiveBackPostCommitAnim() {
         prepareControls();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            InsetsSourceControl ime = createControl(ID_IME, ime());
             // show ime as initial state
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             mController.cancelExistingAnimations(); // fast forward show animation
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
             assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1008,12 +1113,20 @@
             assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
 
             // verify show request is ignored during pre commit phase of predictive back anim
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            } else {
+                mController.onControlsChanged(new InsetsSourceControl[]{ime});
+            }
             assertEquals(ANIMATION_TYPE_USER, mController.getAnimationType(ime()));
 
             // verify show request is applied during post commit phase of predictive back anim
             mController.setPredictiveBackImeHideAnimInProgress(true);
-            mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, null /* statsToken */);
+            } else {
+                mController.show(ime(), false /* fromIme */, null /* statsToken */);
+            }
             assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
 
             // additionally verify that IME ends up visible
@@ -1027,7 +1140,11 @@
         prepareControls();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // show ime as initial state
-            mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
             mController.cancelExistingAnimations(); // fast forward show animation
             mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
             assertTrue(mController.getState().peekSource(ID_IME).isVisible());
@@ -1050,6 +1167,37 @@
         });
     }
 
+    @Test
+    public void testPredictiveBackControlRequestCancelledDuringImeHideAnim() {
+        prepareControls();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // show ime as initial state
+            if (!Flags.refactorInsetsController()) {
+                mController.show(ime(), true /* fromIme */, ImeTracker.Token.empty());
+            } else {
+                mController.show(ime(), false /* fromIme */, ImeTracker.Token.empty());
+            }
+            mController.cancelExistingAnimations(); // fast forward show animation
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+            assertTrue(mController.getState().peekSource(ID_IME).isVisible());
+
+            // start IME hide animation
+            mController.hide(ime(), true /* fromIme */, null /* statsToken */);
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ime()));
+
+            // start control request (for predictive back animation)
+            WindowInsetsAnimationControlListener listener =
+                    mock(WindowInsetsAnimationControlListener.class);
+            mController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null,
+                    listener, /*fromIme*/ false, /*duration*/ -1, /*interpolator*/ null,
+                    ANIMATION_TYPE_USER, /*fromPredictiveBack*/ true);
+
+            // verify that control request is cancelled and animation type remains HIDE
+            verify(listener).onCancelled(any());
+            assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ime()));
+        });
+    }
+
     private void waitUntilNextFrame() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT,
@@ -1058,11 +1206,15 @@
     }
 
     private InsetsSourceControl createControl(int id, @InsetsType int type) {
+        return createControl(id, type, true);
+    }
+
+    private InsetsSourceControl createControl(int id, @InsetsType int type, boolean hasLeash) {
 
         // Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
         // attempt to release mLeash directly.
         SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
-        return new InsetsSourceControl(id, type, copy,
+        return new InsetsSourceControl(id, type, hasLeash ? copy : null,
                 (type & WindowInsets.Type.defaultVisible()) != 0, new Point(), Insets.NONE);
     }
 
@@ -1071,9 +1223,13 @@
     }
 
     private InsetsSourceControl[] prepareControls() {
+        return prepareControls(true);
+    }
+
+    private InsetsSourceControl[] prepareControls(boolean imeControlHasLeash) {
         final InsetsSourceControl navBar = createControl(ID_NAVIGATION_BAR, navigationBars());
         final InsetsSourceControl statusBar = createControl(ID_STATUS_BAR, statusBars());
-        final InsetsSourceControl ime = createControl(ID_IME, ime());
+        final InsetsSourceControl ime = createControl(ID_IME, ime(), imeControlHasLeash);
 
         InsetsSourceControl[] controls = new InsetsSourceControl[3];
         controls[0] = navBar;
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/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
index 66de3d7..397cdcf 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
@@ -124,6 +124,16 @@
             return this;
         }
 
+        @Override
+        public void addFrozenStateChangeCallback(IFrozenStateChangeCallback callback)
+                throws RemoteException {
+        }
+
+        @Override
+        public boolean removeFrozenStateChangeCallback(IFrozenStateChangeCallback callback) {
+            return false;
+        }
+
         public void die() {
             isAlive = false;
             if (mRecipient != null) {
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..149e132 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,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.os.UserHandle;
 
@@ -37,18 +40,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 Icon dummyIcon = Icon.createWithResource(dummyIconPackageName, 123);
         final int dummyIconLevel = 1;
         final int dummyIconNumber = 2;
         final CharSequence dummyIconContentDescription = "dummyIcon";
-        final StatusBarIcon original = new StatusBarIcon(dummyIconPackageName, dummyUserHandle,
-                dummyIconId, dummyIconLevel, dummyIconNumber, dummyIconContentDescription,
-                StatusBarIcon.Type.SystemIcon);
+        return new StatusBarIcon(
+                dummyUserHandle,
+                dummyIconPackageName,
+                dummyIcon,
+                dummyIconLevel,
+                dummyIconNumber,
+                dummyIconContentDescription,
+                StatusBarIcon.Type.SystemIcon,
+                StatusBarIcon.Shape.FIXED_SPACE);
+    }
 
-        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 +96,18 @@
         assertThat(copy.visible).isEqualTo(original.visible);
         assertThat(copy.number).isEqualTo(original.number);
         assertThat(copy.contentDescription).isEqualTo(original.contentDescription);
+        assertThat(copy.type).isEqualTo(original.type);
+        assertThat(copy.shape).isEqualTo(original.shape);
     }
 
-    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/core/tests/hdmitests/Android.bp b/core/tests/hdmitests/Android.bp
index 5f6eaf9..e11bc55 100644
--- a/core/tests/hdmitests/Android.bp
+++ b/core/tests/hdmitests/Android.bp
@@ -37,3 +37,10 @@
     certificate: "platform",
     test_suites: ["device-tests"],
 }
+
+test_module_config {
+    name: "HdmiCecTests_hardware_hdmi",
+    base: "HdmiCecTests",
+    test_suites: ["device-tests"],
+    include_filters: ["android.hardware.hdmi"],
+}
diff --git a/core/tests/mockingcoretests/Android.bp b/core/tests/mockingcoretests/Android.bp
index aca52a8..7fd813a 100644
--- a/core/tests/mockingcoretests/Android.bp
+++ b/core/tests/mockingcoretests/Android.bp
@@ -63,3 +63,13 @@
 
     certificate: "platform",
 }
+
+test_module_config {
+    name: "FrameworksMockingCoreTests_os_bundlerecyclingtest",
+    base: "FrameworksMockingCoreTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["android.os.BundleRecyclingTest"],
+}
diff --git a/core/tests/resourceflaggingtests/Android.bp b/core/tests/resourceflaggingtests/Android.bp
index efb8437..40bdc2b 100644
--- a/core/tests/resourceflaggingtests/Android.bp
+++ b/core/tests/resourceflaggingtests/Android.bp
@@ -26,6 +26,7 @@
     name: "ResourceFlaggingTests",
     srcs: [
         "src/**/*.java",
+        ":resource-flagging-test-app-r-java",
     ],
     platform_apis: true,
     certificate: "platform",
diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
index 471b402..005538a 100644
--- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
+++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
@@ -19,15 +19,22 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.FileUtils;
 import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.intenal.flaggedresources.R;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,7 +53,14 @@
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
         AssetManager assets = new AssetManager();
-        assertThat(assets.addAssetPath(extractApkAndGetPath(R.raw.resapp))).isNotEqualTo(0);
+        assets.setApkAssets(
+                new ApkAssets[]{
+                        ApkAssets.loadFromPath(
+                                extractApkAndGetPath(
+                                        com.android.resourceflaggingtests.R.raw.resapp
+                                )
+                        )
+                }, true);
 
         final DisplayMetrics dm = new DisplayMetrics();
         dm.setToDefaults();
@@ -55,54 +69,60 @@
 
     @Test
     public void testFlagDisabled() {
-        assertThat(getBoolean("res1")).isTrue();
+        assertThat(mResources.getBoolean(R.bool.bool1)).isTrue();
     }
 
     @Test
     public void testFlagEnabled() {
-        assertThat(getBoolean("res2")).isTrue();
+        assertThat(mResources.getBoolean(R.bool.bool2)).isTrue();
     }
 
     @Test
     public void testFlagEnabledDifferentCompilationUnit() {
-        assertThat(getBoolean("res3")).isTrue();
+        assertThat(mResources.getBoolean(R.bool.bool3)).isTrue();
     }
 
     @Test
     public void testFlagDisabledStringArrayElement() {
-        assertThat(getStringArray("strarr1")).isEqualTo(new String[]{"one", "two", "three"});
+        assertThat(mResources.getStringArray(R.array.strarr1))
+                .isEqualTo(new String[]{"one", "two", "three"});
     }
 
     @Test
     public void testFlagDisabledIntArrayElement() {
-        assertThat(getIntArray("intarr1")).isEqualTo(new int[]{1, 2, 3});
+        assertThat(mResources.getIntArray(R.array.intarr1)).isEqualTo(new int[]{1, 2, 3});
     }
 
-    private boolean getBoolean(String name) {
-        int resId = mResources.getIdentifier(
-                name,
-                "bool",
-                "com.android.intenal.flaggedresources");
-        assertThat(resId).isNotEqualTo(0);
-        return mResources.getBoolean(resId);
+    @Test
+    public void testLayoutWithDisabledElements() {
+        LinearLayout ll = (LinearLayout) getLayoutInflater().inflate(R.layout.layout1, null);
+        assertThat(ll).isNotNull();
+        assertThat((View) ll.findViewById(R.id.text1)).isNotNull();
+        assertThat((View) ll.findViewById(R.id.disabled_text)).isNull();
+        assertThat((View) ll.findViewById(R.id.text2)).isNotNull();
     }
 
-    private String[] getStringArray(String name) {
-        int resId = mResources.getIdentifier(
-                name,
-                "array",
-                "com.android.intenal.flaggedresources");
-        assertThat(resId).isNotEqualTo(0);
-        return mResources.getStringArray(resId);
-    }
+    private LayoutInflater getLayoutInflater() {
+        ContextWrapper c = new ContextWrapper(mContext) {
+            private LayoutInflater mInflater;
 
-    private int[] getIntArray(String name) {
-        int resId = mResources.getIdentifier(
-                name,
-                "array",
-                "com.android.intenal.flaggedresources");
-        assertThat(resId).isNotEqualTo(0);
-        return mResources.getIntArray(resId);
+            @Override
+            public Resources getResources() {
+                return mResources;
+            }
+
+            @Override
+            public Object getSystemService(String name) {
+                if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+                    if (mInflater == null) {
+                        mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
+                    }
+                    return mInflater;
+                }
+                return super.getSystemService(name);
+            }
+        };
+        return LayoutInflater.from(c);
     }
 
     private String extractApkAndGetPath(int id) throws Exception {
diff --git a/core/tests/timetests/Android.bp b/core/tests/timetests/Android.bp
index 51181a8..c33d5ee 100644
--- a/core/tests/timetests/Android.bp
+++ b/core/tests/timetests/Android.bp
@@ -23,3 +23,17 @@
     certificate: "platform",
     test_suites: ["device-tests"],
 }
+
+test_module_config {
+    name: "FrameworksTimeCoreTests_android_app",
+    base: "FrameworksTimeCoreTests",
+    test_suites: ["device-tests"],
+    include_filters: ["android.app."],
+}
+
+test_module_config {
+    name: "FrameworksTimeCoreTests_android_service",
+    base: "FrameworksTimeCoreTests",
+    test_suites: ["device-tests"],
+    include_filters: ["android.service."],
+}
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index 4f76dd6..f5b04ee 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -430,6 +430,86 @@
     }
 
     @Test
+    public void cropToLength_waveform_underLength() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[]{0, 1, 2},
+                /* repeatIndex= */ -1);
+        VibrationEffect result = effect.cropToLengthOrNull(5);
+
+        assertThat(result).isEqualTo(effect); // unchanged
+    }
+
+    @Test
+    public void cropToLength_waveform_overLength() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[]{0, 1, 2, 3, 4, 5, 6},
+                /* repeatIndex= */ -1);
+        VibrationEffect result = effect.cropToLengthOrNull(4);
+
+        assertThat(result).isEqualTo(VibrationEffect.createWaveform(
+                new long[]{0, 1, 2, 3},
+                -1));
+    }
+
+    @Test
+    public void cropToLength_waveform_repeating() {
+        // repeating waveforms cannot be truncated
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[]{0, 1, 2, 3, 4, 5, 6},
+                /* repeatIndex= */ 2);
+        VibrationEffect result = effect.cropToLengthOrNull(3);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void cropToLength_waveform_withAmplitudes() {
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                /* timings= */ new long[]{0, 1, 2, 3, 4, 5, 6},
+                /* amplitudes= */ new int[]{10, 20, 40, 10, 20, 40, 10},
+                /* repeatIndex= */ -1);
+        VibrationEffect result = effect.cropToLengthOrNull(3);
+
+        assertThat(result).isEqualTo(VibrationEffect.createWaveform(
+                new long[]{0, 1, 2},
+                new int[]{10, 20, 40},
+                -1));
+    }
+
+    @Test
+    public void cropToLength_composed() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+                .compose();
+        VibrationEffect result = effect.cropToLengthOrNull(1);
+
+        assertThat(result).isNotNull();
+        assertThat(result).isEqualTo(VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose());
+    }
+
+    @Test
+    public void cropToLength_composed_repeating() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                .repeatEffectIndefinitely(TEST_ONE_SHOT)
+                .compose();
+        assertThat(effect.cropToLengthOrNull(1)).isNull();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void cropToLength_vendorEffect() {
+        PersistableBundle vendorData = new PersistableBundle();
+        vendorData.putInt("key", 1);
+        VibrationEffect effect = VibrationEffect.createVendorEffect(vendorData);
+
+        assertThat(effect.cropToLengthOrNull(2)).isNull();
+    }
+
+    @Test
     public void getRingtones_noPrebakedRingtones() {
         Resources r = mockRingtoneResources(new String[0]);
         Context context = mockContext(r);
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 1a3a0f6..4edf52b 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -71,14 +71,9 @@
     },
 }
 
-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",
 }
 
 /////////////////////////////////
@@ -94,3 +89,33 @@
         "DroidSansMono.ttf",
     ],
 }
+
+genrule {
+    name: "generate_font_fallback",
+    tools: [":generate_fonts_xml"],
+    tool_files: [
+        "alias.json",
+        "fallback_order.json",
+    ],
+    srcs: [
+        ":CarroisGothicSC",
+        ":ComingSoon",
+        ":CutiveMono",
+        ":DancingScript",
+        ":DroidSansMono",
+        ":Roboto",
+        ":RobotoFlex",
+        ":SourceSansPro",
+        ":noto-fonts",
+    ],
+    exclude_srcs: [
+        "alias.json",
+        "fallback_order.json",
+    ],
+    out: ["font_fallback.xml"],
+    cmd: "$(location :generate_fonts_xml) " +
+        "--alias=$(location alias.json) " +
+        "--fallback=$(location fallback_order.json) " +
+        "$(in) " +
+        "-o $(out)",
+}
diff --git a/data/fonts/alias.json b/data/fonts/alias.json
new file mode 100644
index 0000000..b5b867a
--- /dev/null
+++ b/data/fonts/alias.json
@@ -0,0 +1,37 @@
+[
+  // sans-serif aliases
+  { "name": "arial", "to": "sans-serif" },
+  { "name": "helvetica", "to": "sans-serif" },
+  { "name": "tahoma", "to": "sans-serif" },
+  { "name": "verdana", "to": "sans-serif" },
+  { "name": "sans-serif-black", "to": "sans-serif", "weight": "900" },
+  { "name": "sans-serif-light", "to": "sans-serif", "weight": "300" },
+  { "name": "sans-serif-medium", "to": "sans-serif", "weight": "500" },
+  { "name": "sans-serif-thin", "to": "sans-serif", "weight": "100" },
+
+  // sans-serif-condensed aliases
+  { "name": "sans-serif-condensed-light", "to": "sans-serif-condensed", "weight": "300" },
+  { "name": "sans-serif-condensed-medium", "to": "sans-serif-condensed", "weight": "500" },
+
+  // serif aliases
+  { "name": "ITC Stone Serif", "to": "serif" },
+  { "name": "baskerville", "to": "serif" },
+  { "name": "fantasy", "to": "serif" },
+  { "name": "georgia", "to": "serif" },
+  { "name": "goudy", "to": "serif" },
+  { "name": "palatino", "to": "serif" },
+  { "name": "times new roman", "to": "serif" },
+  { "name": "times", "to": "serif" },
+  { "name": "serif-bold", "to": "serif", "weight": "700" },
+
+  // monospace aliases
+  { "name": "monaco", "to": "monospace" },
+  { "name": "sans-serif-monospace", "to": "monospace" },
+
+  // serif-monospace aliases
+  { "name": "courier new", "to": "serif-monospace" },
+  { "name": "courier", "to": "serif-monospace" },
+
+  // source-sans-pro aliases
+  { "name": "source-sans-pro-semi-bold", "to": "source-sans-pro", "weight": "600" }
+]
diff --git a/data/fonts/fallback_order.json b/data/fonts/fallback_order.json
new file mode 100644
index 0000000..2fc3f3e
--- /dev/null
+++ b/data/fonts/fallback_order.json
@@ -0,0 +1,136 @@
+[
+  { "lang": "und-Arab" },
+  { "lang": "und-Ethi" },
+  { "lang": "und-Hebr" },
+  { "lang": "und-Thai" },
+  { "lang": "und-Armn" },
+  { "lang": "und-Geor,und-Geok" },
+  { "lang": "und-Deva" },
+  { "lang": "und-Gujr" },
+  { "lang": "und-Guru" },
+  { "lang": "und-Taml" },
+  { "lang": "und-Mlym" },
+  { "lang": "und-Beng" },
+  { "lang": "und-Telu" },
+  { "lang": "und-Knda" },
+  { "lang": "und-Orya" },
+  { "lang": "und-Sinh" },
+  { "lang": "und-Khmr" },
+  { "lang": "und-Laoo" },
+  { "lang": "und-Mymr" },
+  { "lang": "und-Thaa" },
+  { "lang": "und-Cham" },
+  { "lang": "und-Ahom" },
+  { "lang": "und-Adlm" },
+  { "lang": "und-Avst" },
+  { "lang": "und-Bali" },
+  { "lang": "und-Bamu" },
+  { "lang": "und-Batk" },
+  { "lang": "und-Brah" },
+  { "lang": "und-Bugi" },
+  { "lang": "und-Buhd" },
+  { "lang": "und-Cans" },
+  { "lang": "und-Cari" },
+  { "lang": "und-Cakm" },
+  { "lang": "und-Cher" },
+  { "lang": "und-Copt" },
+  { "lang": "und-Xsux" },
+  { "lang": "und-Cprt" },
+  { "lang": "und-Dsrt" },
+  { "lang": "und-Egyp" },
+  { "lang": "und-Elba" },
+  { "lang": "und-Glag" },
+  { "lang": "und-Goth" },
+  { "lang": "und-Hano" },
+  { "lang": "und-Armi" },
+  { "lang": "und-Phli" },
+  { "lang": "und-Prti" },
+  { "lang": "und-Java" },
+  { "lang": "und-Kthi" },
+  { "lang": "und-Kali" },
+  { "lang": "und-Khar" },
+  { "lang": "und-Lepc" },
+  { "lang": "und-Limb" },
+  { "lang": "und-Linb" },
+  { "lang": "und-Lisu" },
+  { "lang": "und-Lyci" },
+  { "lang": "und-Lydi" },
+  { "lang": "und-Mand" },
+  { "lang": "und-Mtei" },
+  { "lang": "und-Talu" },
+  { "lang": "und-Nkoo" },
+  { "lang": "und-Ogam" },
+  { "lang": "und-Olck" },
+  { "lang": "und-Ital" },
+  { "lang": "und-Xpeo" },
+  { "lang": "und-Sarb" },
+  { "lang": "und-Orkh" },
+  { "lang": "und-Osge" },
+  { "lang": "und-Osma" },
+  { "lang": "und-Phnx" },
+  { "lang": "und-Rjng" },
+  { "lang": "und-Runr" },
+  { "lang": "und-Samr" },
+  { "lang": "und-Saur" },
+  { "lang": "und-Shaw" },
+  { "lang": "und-Sund" },
+  { "lang": "und-Sylo" },
+  { "lang": "und-Syre" },
+  { "lang": "und-Syrn" },
+  { "lang": "und-Syrj" },
+  { "lang": "und-Tglg" },
+  { "lang": "und-Tagb" },
+  { "lang": "und-Lana" },
+  { "lang": "und-Tavt" },
+  { "lang": "und-Tibt" },
+  { "lang": "und-Tfng" },
+  { "lang": "und-Ugar" },
+  { "lang": "und-Vaii" },
+  // NotoSansSymbol-Regular-Subsetted doesn't have any language but should be
+  // placed before the CJK fonts for reproducing the same fallback order.
+  { "id": "NotoSansSymbols-Regular-Subsetted" },
+  { "lang": "zh-Hans" },
+  { "lang": "zh-Hant,zh-Bopo" },
+  { "lang": "ja" },
+  { "lang": "ko" },
+  { "lang": "und-Zsye" },
+  { "lang": "und-Zsym" },
+  { "lang": "und-Tale" },
+  { "lang": "und-Yiii" },
+  { "lang": "und-Mong" },
+  { "lang": "und-Phag" },
+  { "lang": "und-Hluw" },
+  { "lang": "und-Bass" },
+  { "lang": "und-Bhks" },
+  { "lang": "und-Hatr" },
+  { "lang": "und-Lina" },
+  { "lang": "und-Mani" },
+  { "lang": "und-Marc" },
+  { "lang": "und-Merc" },
+  { "lang": "und-Plrd" },
+  { "lang": "und-Mroo" },
+  { "lang": "und-Mult" },
+  { "lang": "und-Nbat" },
+  { "lang": "und-Newa" },
+  { "lang": "und-Narb" },
+  { "lang": "und-Perm" },
+  { "lang": "und-Hmng" },
+  { "lang": "und-Palm" },
+  { "lang": "und-Pauc" },
+  { "lang": "und-Shrd" },
+  { "lang": "und-Sora" },
+  { "lang": "und-Gong" },
+  { "lang": "und-Rohg" },
+  { "lang": "und-Khoj" },
+  { "lang": "und-Gonm" },
+  { "lang": "und-Wcho" },
+  { "lang": "und-Wara" },
+  { "lang": "und-Gran" },
+  { "lang": "und-Modi" },
+  { "lang": "und-Dogr" },
+  { "lang": "und-Medf" },
+  { "lang": "und-Soyo" },
+  { "lang": "und-Takr" },
+  { "lang": "und-Hmnp" },
+  { "lang": "und-Yezi" }
+]
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/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
index a99e201..683f614 100644
--- a/graphics/java/android/graphics/Matrix44.java
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -19,6 +19,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import com.android.graphics.hwui.flags.Flags;
 
@@ -30,6 +31,7 @@
  * in row-major order. The values and operations are treated as column vectors.
  */
 @FlaggedApi(Flags.FLAG_MATRIX_44)
+@RavenwoodKeepWholeClass
 public class Matrix44 {
     final float[] mBackingArray;
     /**
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 618e6dc..c7b8941 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.drawable.Drawable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -35,6 +36,7 @@
  * @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider)
  * @see Drawable#getOutline(Outline)
  */
+@RavenwoodKeepWholeClass
 public final class Outline {
     private static final float RADIUS_UNDEFINED = Float.NEGATIVE_INFINITY;
 
diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java
index 748d66c..76c17154 100644
--- a/graphics/java/android/graphics/ParcelableColorSpace.java
+++ b/graphics/java/android/graphics/ParcelableColorSpace.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 /**
  * A {@link Parcelable} wrapper for a {@link ColorSpace}. In order to enable parceling, the
@@ -27,6 +28,7 @@
  * {@link ColorSpace.Rgb} instance that has an ICC parametric transfer function as returned by
  * {@link ColorSpace.Rgb#getTransferParameters()}.
  */
+@RavenwoodKeepWholeClass
 public final class ParcelableColorSpace implements Parcelable {
     private final ColorSpace mColorSpace;
 
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index 3ec5b9c..a872e03 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -17,10 +17,12 @@
 package android.graphics;
 
 import android.annotation.IntDef;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+@RavenwoodKeepWholeClass
 public class PixelFormat {
     /** @hide */
     @IntDef({UNKNOWN, TRANSLUCENT, TRANSPARENT, OPAQUE})
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..4ce2942 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;
 
 /**
@@ -57,7 +58,7 @@
     void scheduleBackup() {
         if (!mBackupIdlerScheduled) {
             mBackupIdlerScheduled = true;
-            Looper.myQueue().addIdleHandler(mBackupIdler);
+            Looper.getMainLooper().getQueue().addIdleHandler(mBackupIdler);
         }
     }
 
@@ -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/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 1eb95c1..9ea2943 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -70,6 +70,10 @@
     @NonNull
     private final TaskFragmentCallback mCallback;
 
+    @VisibleForTesting
+    @Nullable
+    TaskFragmentAnimationController mAnimationController;
+
     /**
      * Callback that notifies the controller about changes to task fragments.
      */
@@ -87,6 +91,25 @@
         mCallback = callback;
     }
 
+    @Override
+    public void unregisterOrganizer() {
+        if (mAnimationController != null) {
+            mAnimationController.unregisterRemoteAnimations();
+            mAnimationController = null;
+        }
+        super.unregisterOrganizer();
+    }
+
+    /**
+     * Overrides the animation for transitions of embedded activities organized by this organizer.
+     */
+    void overrideSplitAnimation() {
+        if (mAnimationController == null) {
+            mAnimationController = new TaskFragmentAnimationController(this);
+        }
+        mAnimationController.registerRemoteAnimations();
+    }
+
     /**
      * Starts a new Activity and puts it into split with an existing Activity side-by-side.
      * @param launchingFragmentToken    token for the launching TaskFragment. If it exists, it will
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/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 24b56ae..f2f2b7ea 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -58,18 +58,22 @@
 import android.app.ActivityClient;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
+import android.app.AppGlobals;
 import android.app.Application;
 import android.app.Instrumentation;
 import android.app.servertransaction.ClientTransactionListenerController;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -116,6 +120,7 @@
 public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
         ActivityEmbeddingComponent, DividerPresenter.DragEventCallback {
     static final String TAG = "SplitController";
+    static final boolean ENABLE_SHELL_TRANSITIONS = getShellTransitEnabled();
 
     // TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
     //  association. It's not set in WM Extensions nor Wm Jetpack library currently.
@@ -919,7 +924,8 @@
 
         // Update all TaskFragments in the Task. Make a copy of the list since some may be
         // removed on updating.
-        final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
+        final List<TaskFragmentContainer> containers
+                = new ArrayList<>(taskContainer.getTaskFragmentContainers());
         for (int i = containers.size() - 1; i >= 0; i--) {
             final TaskFragmentContainer container = containers.get(i);
             // Wait until onTaskFragmentAppeared to update new container.
@@ -3307,4 +3313,17 @@
             transactionRecord.apply(false /* shouldApplyIndependently */);
         }
     }
+
+    // TODO(b/207070762): cleanup with legacy app transition
+    private static boolean getShellTransitEnabled() {
+        try {
+            if (AppGlobals.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_AUTOMOTIVE, 0)) {
+                return SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+            }
+        } catch (RemoteException re) {
+            Log.w(TAG, "Error getting system features");
+        }
+        return true;
+    }
 }
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..abc7b29 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -169,12 +169,17 @@
         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();
         }
         mBackupHelper = new BackupHelper(controller, outSavedState);
+        if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
+            // TODO(b/207070762): cleanup with legacy app transition
+            // Animation will be handled by WM Shell when Shell transition is enabled.
+            overrideSplitAnimation();
+        }
     }
 
     void scheduleBackup() {
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..608a3be 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,16 +128,15 @@
      * @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(
                 taskProperties.getConfiguration(),
                 taskProperties.getDisplayId(),
+                taskId,
                 // Note that it is always called when there's a new Activity is started, which
                 // implies the host task is visible and has an activity in the task.
                 true /* visible */,
@@ -148,8 +145,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() {
@@ -162,7 +195,8 @@
 
     void setInvisible() {
         mInfo = new TaskFragmentParentInfo(mInfo.getConfiguration(), mInfo.getDisplayId(),
-                false /* visible */, mInfo.hasDirectActivity(), mInfo.getDecorSurface());
+                mInfo.getTaskId(), false /* visible */, mInfo.hasDirectActivity(),
+                mInfo.getDecorSurface());
     }
 
     boolean hasDirectActivity() {
@@ -680,34 +714,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/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
new file mode 100644
index 0000000..33220c4
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.graphics.Matrix.MTRANS_X;
+import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Choreographer;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}.
+ *
+ * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close.
+ */
+class TaskFragmentAnimationAdapter {
+
+    /**
+     * If {@link #mOverrideLayer} is set to this value, we don't want to override the surface layer.
+     */
+    private static final int LAYER_NO_OVERRIDE = -1;
+
+    @NonNull
+    final Animation mAnimation;
+    @NonNull
+    final RemoteAnimationTarget mTarget;
+    @NonNull
+    final SurfaceControl mLeash;
+    /** Area in absolute coordinate that the animation surface shouldn't go beyond. */
+    @NonNull
+    private final Rect mWholeAnimationBounds = new Rect();
+    /**
+     * Area in absolute coordinate that should represent all the content to show for this window.
+     * This should be the end bounds for opening window, and start bounds for closing window in case
+     * the window is resizing during the open/close transition.
+     */
+    @NonNull
+    private final Rect mContentBounds = new Rect();
+    /** Offset relative to the window parent surface for {@link #mContentBounds}. */
+    @NonNull
+    private final Point mContentRelOffset = new Point();
+
+    @NonNull
+    final Transformation mTransformation = new Transformation();
+    @NonNull
+    final float[] mMatrix = new float[9];
+    @NonNull
+    final float[] mVecs = new float[4];
+    @NonNull
+    final Rect mRect = new Rect();
+    private boolean mIsFirstFrame = true;
+    private int mOverrideLayer = LAYER_NO_OVERRIDE;
+
+    TaskFragmentAnimationAdapter(@NonNull Animation animation,
+            @NonNull RemoteAnimationTarget target) {
+        this(animation, target, target.leash, target.screenSpaceBounds);
+    }
+
+    /**
+     * @param leash the surface to animate.
+     * @param wholeAnimationBounds  area in absolute coordinate that the animation surface shouldn't
+     *                              go beyond.
+     */
+    TaskFragmentAnimationAdapter(@NonNull Animation animation,
+            @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash,
+            @NonNull Rect wholeAnimationBounds) {
+        mAnimation = animation;
+        mTarget = target;
+        mLeash = leash;
+        mWholeAnimationBounds.set(wholeAnimationBounds);
+        if (target.mode == MODE_CLOSING) {
+            // When it is closing, we want to show the content at the start position in case the
+            // window is resizing as well. For example, when the activities is changing from split
+            // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
+            final Rect startBounds = target.startBounds;
+            final Rect endBounds = target.screenSpaceBounds;
+            mContentBounds.set(startBounds);
+            mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+            mContentRelOffset.offset(
+                    startBounds.left - endBounds.left,
+                    startBounds.top - endBounds.top);
+        } else {
+            mContentBounds.set(target.screenSpaceBounds);
+            mContentRelOffset.set(target.localBounds.left, target.localBounds.top);
+        }
+    }
+
+    /**
+     * Surface layer to be set at the first frame of the animation. We will not set the layer if it
+     * is set to {@link #LAYER_NO_OVERRIDE}.
+     */
+    final void overrideLayer(int layer) {
+        mOverrideLayer = layer;
+    }
+
+    /** Called on frame update. */
+    final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) {
+        if (mIsFirstFrame) {
+            t.show(mLeash);
+            if (mOverrideLayer != LAYER_NO_OVERRIDE) {
+                t.setLayer(mLeash, mOverrideLayer);
+            }
+            mIsFirstFrame = false;
+        }
+
+        // Extract the transformation to the current time.
+        mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()),
+                mTransformation);
+        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+        onAnimationUpdateInner(t);
+    }
+
+    /** To be overridden by subclasses to adjust the animation surface change. */
+    void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+        // Update the surface position and alpha.
+        mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
+        t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+        t.setAlpha(mLeash, mTransformation.getAlpha());
+
+        // Get current surface bounds in absolute coordinate.
+        // positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
+        final int positionX = Math.round(mMatrix[MTRANS_X]);
+        final int positionY = Math.round(mMatrix[MTRANS_Y]);
+        final Rect cropRect = new Rect(mContentBounds);
+        cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
+
+        // Store the current offset of the surface top left from (0,0) in absolute coordinate.
+        final int offsetX = cropRect.left;
+        final int offsetY = cropRect.top;
+
+        // Intersect to make sure the animation happens within the whole animation bounds.
+        if (!cropRect.intersect(mWholeAnimationBounds)) {
+            // Hide the surface when it is outside of the animation area.
+            t.setAlpha(mLeash, 0);
+        }
+
+        // cropRect is in absolute coordinate, so we need to translate it to surface top left.
+        cropRect.offset(-offsetX, -offsetY);
+        t.setCrop(mLeash, cropRect);
+    }
+
+    /** Called after animation finished. */
+    final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
+        onAnimationUpdate(t, mAnimation.getDuration());
+    }
+
+    final long getDurationHint() {
+        return mAnimation.computeDurationHint();
+    }
+
+    /**
+     * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has
+     * size change.
+     */
+    static class SnapshotAdapter extends TaskFragmentAnimationAdapter {
+
+        SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
+            // Start leash is the snapshot of the starting surface.
+            super(animation, target, target.startLeash, target.screenSpaceBounds);
+        }
+
+        @Override
+        void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+            // Snapshot should always be placed at the top left of the animation leash.
+            mTransformation.getMatrix().postTranslate(0, 0);
+            t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+            t.setAlpha(mLeash, mTransformation.getAlpha());
+        }
+    }
+
+    /**
+     * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change.
+     */
+    static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter {
+
+        BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
+            super(animation, target);
+        }
+
+        @Override
+        void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
+            mTransformation.getMatrix().postTranslate(
+                    mTarget.localBounds.left, mTarget.localBounds.top);
+            t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
+            t.setAlpha(mLeash, mTransformation.getAlpha());
+
+            // The following applies an inverse scale to the clip-rect so that it crops "after" the
+            // scale instead of before.
+            mVecs[1] = mVecs[2] = 0;
+            mVecs[0] = mVecs[3] = 1;
+            mTransformation.getMatrix().mapVectors(mVecs);
+            mVecs[0] = 1.f / mVecs[0];
+            mVecs[3] = 1.f / mVecs[3];
+            final Rect clipRect = mTransformation.getClipRect();
+            mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
+            mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
+            mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
+            mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
+            t.setWindowCrop(mLeash, mRect);
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
new file mode 100644
index 0000000..d7eb9a0
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
+
+import android.util.Log;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationDefinition;
+import android.window.TaskFragmentOrganizer;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/** Controls the TaskFragment remote animations. */
+class TaskFragmentAnimationController {
+
+    private static final String TAG = "TaskFragAnimationCtrl";
+    static final boolean DEBUG = false;
+
+    private final TaskFragmentOrganizer mOrganizer;
+    private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
+    @VisibleForTesting
+    final RemoteAnimationDefinition mDefinition;
+    private boolean mIsRegistered;
+
+    TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
+        mOrganizer = organizer;
+        mDefinition = new RemoteAnimationDefinition();
+        final RemoteAnimationAdapter animationAdapter =
+                new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
+        mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
+    }
+
+    void registerRemoteAnimations() {
+        if (DEBUG) {
+            Log.v(TAG, "registerRemoteAnimations");
+        }
+        if (mIsRegistered) {
+            return;
+        }
+        mOrganizer.registerRemoteAnimations(mDefinition);
+        mIsRegistered = true;
+    }
+
+    void unregisterRemoteAnimations() {
+        if (DEBUG) {
+            Log.v(TAG, "unregisterRemoteAnimations");
+        }
+        if (!mIsRegistered) {
+            return;
+        }
+        mOrganizer.unregisterRemoteAnimations();
+        mIsRegistered = false;
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
new file mode 100644
index 0000000..d9b73a8
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_CHANGING;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
+import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+
+/** To run the TaskFragment animations. */
+class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
+
+    private static final String TAG = "TaskFragAnimationRunner";
+    private final Handler mHandler;
+    private final TaskFragmentAnimationSpec mAnimationSpec;
+
+    TaskFragmentAnimationRunner() {
+        HandlerThread animationThread = new HandlerThread(
+                "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY);
+        animationThread.start();
+        mHandler = animationThread.getThreadHandler();
+        mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
+    }
+
+    @Nullable
+    private Animator mAnimator;
+
+    @Override
+    public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+            @NonNull RemoteAnimationTarget[] apps,
+            @NonNull RemoteAnimationTarget[] wallpapers,
+            @NonNull RemoteAnimationTarget[] nonApps,
+            @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+        if (wallpapers.length != 0 || nonApps.length != 0) {
+            throw new IllegalArgumentException("TaskFragment shouldn't handle animation with"
+                    + "wallpaper or non-app windows.");
+        }
+        if (TaskFragmentAnimationController.DEBUG) {
+            Log.v(TAG, "onAnimationStart transit=" + transit);
+        }
+        mHandler.post(() -> startAnimation(transit, apps, finishedCallback));
+    }
+
+    @Override
+    public void onAnimationCancelled() {
+        mHandler.post(this::cancelAnimation);
+    }
+
+    /** Creates and starts animation. */
+    private void startAnimation(@WindowManager.TransitionOldType int transit,
+            @NonNull RemoteAnimationTarget[] targets,
+            @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+        if (mAnimator != null) {
+            Log.w(TAG, "start new animation when the previous one is not finished yet.");
+            mAnimator.cancel();
+        }
+        mAnimator = createAnimator(transit, targets, finishedCallback);
+        mAnimator.start();
+    }
+
+    /** Cancels animation. */
+    private void cancelAnimation() {
+        if (mAnimator == null) {
+            return;
+        }
+        mAnimator.cancel();
+        mAnimator = null;
+    }
+
+    /** Creates the animator given the transition type and windows. */
+    @NonNull
+    private Animator createAnimator(@WindowManager.TransitionOldType int transit,
+            @NonNull RemoteAnimationTarget[] targets,
+            @NonNull IRemoteAnimationFinishedCallback finishedCallback) {
+        final List<TaskFragmentAnimationAdapter> adapters =
+                createAnimationAdapters(transit, targets);
+        long duration = 0;
+        for (TaskFragmentAnimationAdapter adapter : adapters) {
+            duration = Math.max(duration, adapter.getDurationHint());
+        }
+        final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+        animator.setDuration(duration);
+        animator.addUpdateListener((anim) -> {
+            // Update all adapters in the same transaction.
+            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            for (TaskFragmentAnimationAdapter adapter : adapters) {
+                adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+            }
+            t.apply();
+        });
+        animator.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {}
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                for (TaskFragmentAnimationAdapter adapter : adapters) {
+                    adapter.onAnimationEnd(t);
+                }
+                t.apply();
+
+                try {
+                    finishedCallback.onAnimationFinished();
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+                mAnimator = null;
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {}
+
+            @Override
+            public void onAnimationRepeat(Animator animation) {}
+        });
+        return animator;
+    }
+
+    /** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
+    @NonNull
+    private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
+            @WindowManager.TransitionOldType int transit,
+            @NonNull RemoteAnimationTarget[] targets) {
+        switch (transit) {
+            case TRANSIT_OLD_ACTIVITY_OPEN:
+            case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
+                return createOpenAnimationAdapters(targets);
+            case TRANSIT_OLD_ACTIVITY_CLOSE:
+            case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
+                return createCloseAnimationAdapters(targets);
+            case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
+                return createChangeAnimationAdapters(targets);
+            default:
+                throw new IllegalArgumentException("Unhandled transit type=" + transit);
+        }
+    }
+
+    @NonNull
+    private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
+            @NonNull RemoteAnimationTarget[] targets) {
+        return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
+                mAnimationSpec::loadOpenAnimation);
+    }
+
+    @NonNull
+    private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
+            @NonNull RemoteAnimationTarget[] targets) {
+        return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
+                mAnimationSpec::loadCloseAnimation);
+    }
+
+    /**
+     * Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
+     * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
+     */
+    @NonNull
+    private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
+            @NonNull RemoteAnimationTarget[] targets, boolean isOpening,
+            @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
+        // We need to know if the target window is only a partial of the whole animation screen.
+        // If so, we will need to adjust it to make the whole animation screen looks like one.
+        final List<RemoteAnimationTarget> openingTargets = new ArrayList<>();
+        final List<RemoteAnimationTarget> closingTargets = new ArrayList<>();
+        final Rect openingWholeScreenBounds = new Rect();
+        final Rect closingWholeScreenBounds = new Rect();
+        for (RemoteAnimationTarget target : targets) {
+            if (target.mode != MODE_CLOSING) {
+                openingTargets.add(target);
+                openingWholeScreenBounds.union(target.screenSpaceBounds);
+            } else {
+                closingTargets.add(target);
+                closingWholeScreenBounds.union(target.screenSpaceBounds);
+                // Union the start bounds since this may be the ClosingChanging animation.
+                closingWholeScreenBounds.union(target.startBounds);
+            }
+        }
+
+        // For OPEN transition, open windows should be above close windows.
+        // For CLOSE transition, open windows should be below close windows.
+        int offsetLayer = TYPE_LAYER_OFFSET;
+        final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+        for (RemoteAnimationTarget target : openingTargets) {
+            final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
+                    animationProvider, openingWholeScreenBounds);
+            if (isOpening) {
+                adapter.overrideLayer(offsetLayer++);
+            }
+            adapters.add(adapter);
+        }
+        for (RemoteAnimationTarget target : closingTargets) {
+            final TaskFragmentAnimationAdapter adapter = createOpenCloseAnimationAdapter(target,
+                    animationProvider, closingWholeScreenBounds);
+            if (!isOpening) {
+                adapter.overrideLayer(offsetLayer++);
+            }
+            adapters.add(adapter);
+        }
+        return adapters;
+    }
+
+    @NonNull
+    private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
+            @NonNull RemoteAnimationTarget target,
+            @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
+            @NonNull Rect wholeAnimationBounds) {
+        final Animation animation = animationProvider.apply(target, wholeAnimationBounds);
+        return new TaskFragmentAnimationAdapter(animation, target, target.leash,
+                wholeAnimationBounds);
+    }
+
+    @NonNull
+    private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
+            @NonNull RemoteAnimationTarget[] targets) {
+        if (shouldUseJumpCutForChangeAnimation(targets)) {
+            return new ArrayList<>();
+        }
+
+        final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
+        for (RemoteAnimationTarget target : targets) {
+            if (target.mode == MODE_CHANGING) {
+                // This is the target with bounds change.
+                final Animation[] animations =
+                        mAnimationSpec.createChangeBoundsChangeAnimations(target);
+                // Adapter for the starting snapshot leash.
+                adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter(
+                        animations[0], target));
+                // Adapter for the ending bounds changed leash.
+                adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter(
+                        animations[1], target));
+                continue;
+            }
+
+            // These are the other targets that don't have bounds change in the same transition.
+            final Animation animation;
+            if (target.hasAnimatingParent) {
+                // No-op if it will be covered by the changing parent window.
+                animation = TaskFragmentAnimationSpec.createNoopAnimation(target);
+            } else if (target.mode == MODE_CLOSING) {
+                animation = mAnimationSpec.createChangeBoundsCloseAnimation(target);
+            } else {
+                animation = mAnimationSpec.createChangeBoundsOpenAnimation(target);
+            }
+            adapters.add(new TaskFragmentAnimationAdapter(animation, target));
+        }
+        return adapters;
+    }
+
+    /**
+     * Whether we should use jump cut for the change transition.
+     * This normally happens when opening a new secondary with the existing primary using a
+     * different split layout. This can be complicated, like from horizontal to vertical split with
+     * new split pairs.
+     * Uses a jump cut animation to simplify.
+     */
+    private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
+        boolean hasOpeningWindow = false;
+        boolean hasClosingWindow = false;
+        for (RemoteAnimationTarget target : targets) {
+            if (target.hasAnimatingParent) {
+                continue;
+            }
+            hasOpeningWindow |= target.mode == MODE_OPENING;
+            hasClosingWindow |= target.mode == MODE_CLOSING;
+        }
+        return hasOpeningWindow && hasClosingWindow;
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
new file mode 100644
index 0000000..1f866c3
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.provider.Settings;
+import android.view.RemoteAnimationTarget;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.policy.AttributeCache;
+import com.android.internal.policy.TransitionAnimation;
+
+/** Animation spec for TaskFragment transition. */
+// TODO(b/206557124): provide an easier way to customize animation
+class TaskFragmentAnimationSpec {
+
+    private static final String TAG = "TaskFragAnimationSpec";
+    private static final int CHANGE_ANIMATION_DURATION = 517;
+    private static final int CHANGE_ANIMATION_FADE_DURATION = 80;
+    private static final int CHANGE_ANIMATION_FADE_OFFSET = 30;
+
+    private final Context mContext;
+    private final TransitionAnimation mTransitionAnimation;
+    private final Interpolator mFastOutExtraSlowInInterpolator;
+    private final LinearInterpolator mLinearInterpolator;
+    private float mTransitionAnimationScaleSetting;
+
+    TaskFragmentAnimationSpec(@NonNull Handler handler) {
+        mContext = ActivityThread.currentActivityThread().getApplication();
+        mTransitionAnimation = new TransitionAnimation(mContext, false /* debug */, TAG);
+        // Initialize the AttributeCache for the TransitionAnimation.
+        AttributeCache.init(mContext);
+        mFastOutExtraSlowInInterpolator = AnimationUtils.loadInterpolator(
+                mContext, android.R.interpolator.fast_out_extra_slow_in);
+        mLinearInterpolator = new LinearInterpolator();
+
+        // The transition animation should be adjusted based on the developer option.
+        final ContentResolver resolver = mContext.getContentResolver();
+        mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
+        resolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
+                new SettingsObserver(handler));
+    }
+
+    /** For target that doesn't need to be animated. */
+    @NonNull
+    static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
+        // Noop but just keep the target showing/hiding.
+        final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
+        return new AlphaAnimation(alpha, alpha);
+    }
+
+    /** Animation for target that is opening in a change transition. */
+    @NonNull
+    Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
+        final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+        final Rect bounds = target.screenSpaceBounds;
+        final int startLeft;
+        final int startTop;
+        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+            // The window will be animated in from left or right depending on its position.
+            startTop = 0;
+            startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+        } else {
+            // The window will be animated in from top or bottom depending on its position.
+            startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+            startLeft = 0;
+        }
+
+        // The position should be 0-based as we will post translate in
+        // TaskFragmentAnimationAdapter#onAnimationUpdate
+        final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
+        animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+        animation.setDuration(CHANGE_ANIMATION_DURATION);
+        animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        return animation;
+    }
+
+    /** Animation for target that is closing in a change transition. */
+    @NonNull
+    Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
+        final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+        // Use startBounds if the window is closing in case it may also resize.
+        final Rect bounds = target.startBounds;
+        final int endTop;
+        final int endLeft;
+        if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+            // The window will be animated out to left or right depending on its position.
+            endTop = 0;
+            endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+        } else {
+            // The window will be animated out to top or bottom depending on its position.
+            endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+            endLeft = 0;
+        }
+
+        // The position should be 0-based as we will post translate in
+        // TaskFragmentAnimationAdapter#onAnimationUpdate
+        final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
+        animation.setInterpolator(mFastOutExtraSlowInInterpolator);
+        animation.setDuration(CHANGE_ANIMATION_DURATION);
+        animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
+        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        return animation;
+    }
+
+    /**
+     * Animation for target that is changing (bounds change) in a change transition.
+     * @return the return array always has two elements. The first one is for the start leash, and
+     *         the second one is for the end leash.
+     */
+    @NonNull
+    Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
+        // Both start bounds and end bounds are in screen coordinates. We will post translate
+        // to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
+        final Rect startBounds = target.startBounds;
+        final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+        final Rect endBounds = target.screenSpaceBounds;
+        float scaleX = ((float) startBounds.width()) / endBounds.width();
+        float scaleY = ((float) startBounds.height()) / endBounds.height();
+        // Start leash is a child of the end leash. Reverse the scale so that the start leash won't
+        // be scaled up with its parent.
+        float startScaleX = 1.f / scaleX;
+        float startScaleY = 1.f / scaleY;
+
+        // The start leash will be fade out.
+        final AnimationSet startSet = new AnimationSet(false /* shareInterpolator */);
+        final Animation startAlpha = new AlphaAnimation(1f, 0f);
+        startAlpha.setInterpolator(mLinearInterpolator);
+        startAlpha.setDuration(CHANGE_ANIMATION_FADE_DURATION);
+        startAlpha.setStartOffset(CHANGE_ANIMATION_FADE_OFFSET);
+        startSet.addAnimation(startAlpha);
+        final Animation startScale = new ScaleAnimation(startScaleX, startScaleX, startScaleY,
+                startScaleY);
+        startScale.setInterpolator(mFastOutExtraSlowInInterpolator);
+        startScale.setDuration(CHANGE_ANIMATION_DURATION);
+        startSet.addAnimation(startScale);
+        startSet.initialize(startBounds.width(), startBounds.height(), endBounds.width(),
+                endBounds.height());
+        startSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+        // The end leash will be moved into the end position while scaling.
+        final AnimationSet endSet = new AnimationSet(true /* shareInterpolator */);
+        endSet.setInterpolator(mFastOutExtraSlowInInterpolator);
+        final Animation endScale = new ScaleAnimation(scaleX, 1, scaleY, 1);
+        endScale.setDuration(CHANGE_ANIMATION_DURATION);
+        endSet.addAnimation(endScale);
+        // The position should be 0-based as we will post translate in
+        // TaskFragmentAnimationAdapter#onAnimationUpdate
+        final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
+                startBounds.top - endBounds.top, 0);
+        endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
+        endSet.addAnimation(endTranslate);
+        // The end leash is resizing, we should update the window crop based on the clip rect.
+        final Rect startClip = new Rect(startBounds);
+        final Rect endClip = new Rect(endBounds);
+        startClip.offsetTo(0, 0);
+        endClip.offsetTo(0, 0);
+        final Animation clipAnim = new ClipRectAnimation(startClip, endClip);
+        clipAnim.setDuration(CHANGE_ANIMATION_DURATION);
+        endSet.addAnimation(clipAnim);
+        endSet.initialize(startBounds.width(), startBounds.height(), parentBounds.width(),
+                parentBounds.height());
+        endSet.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+
+        return new Animation[]{startSet, endSet};
+    }
+
+    @NonNull
+    Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
+            @NonNull Rect wholeAnimationBounds) {
+        final boolean isEnter = target.mode != MODE_CLOSING;
+        final Animation animation;
+        // Background color on TaskDisplayArea has already been set earlier in
+        // WindowContainer#getAnimationAdapter.
+        if (target.showBackdrop) {
+            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                    ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
+                    : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
+        } else {
+            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                    ? com.android.internal.R.anim.task_fragment_open_enter
+                    : com.android.internal.R.anim.task_fragment_open_exit);
+        }
+        // Use the whole animation bounds instead of the change bounds, so that when multiple change
+        // targets are opening at the same time, the animation applied to each will be the same.
+        // Otherwise, we may see gap between the activities that are launching together.
+        animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
+                wholeAnimationBounds.width(), wholeAnimationBounds.height());
+        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        return animation;
+    }
+
+    @NonNull
+    Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
+            @NonNull Rect wholeAnimationBounds) {
+        final boolean isEnter = target.mode != MODE_CLOSING;
+        final Animation animation;
+        if (target.showBackdrop) {
+            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                    ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
+                    : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
+        } else {
+            animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+                    ? com.android.internal.R.anim.task_fragment_close_enter
+                    : com.android.internal.R.anim.task_fragment_close_exit);
+        }
+        // Use the whole animation bounds instead of the change bounds, so that when multiple change
+        // targets are closing at the same time, the animation applied to each will be the same.
+        // Otherwise, we may see gap between the activities that are finishing together.
+        animation.initialize(wholeAnimationBounds.width(), wholeAnimationBounds.height(),
+                wholeAnimationBounds.width(), wholeAnimationBounds.height());
+        animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
+        return animation;
+    }
+
+    private float getTransitionAnimationScaleSetting() {
+        return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+                Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+                                R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+    }
+
+    private class SettingsObserver extends ContentObserver {
+        SettingsObserver(@NonNull Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
+        }
+    }
+}
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/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 8911d18..ac004c3 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -23,6 +23,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -84,6 +86,24 @@
     }
 
     @Test
+    public void testUnregisterOrganizer() {
+        mOrganizer.overrideSplitAnimation();
+        mOrganizer.unregisterOrganizer();
+
+        verify(mOrganizer).unregisterRemoteAnimations();
+    }
+
+    @Test
+    public void testOverrideSplitAnimation() {
+        assertNull(mOrganizer.mAnimationController);
+
+        mOrganizer.overrideSplitAnimation();
+
+        assertNotNull(mOrganizer.mAnimationController);
+        verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
+    }
+
+    @Test
     public void testExpandTaskFragment() {
         final TaskContainer taskContainer = createTestTaskContainer();
         doReturn(taskContainer).when(mSplitController).getTaskContainer(anyInt());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 90eeb58..5b97e7e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -549,7 +549,7 @@
         assertThat(taskContainer.getTaskFragmentContainers()).containsExactly(overlayContainer);
 
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(Configuration.EMPTY,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                DEFAULT_DISPLAY, TASK_ID, true /* visible */, false /* hasDirectActivity */,
                 null /* decorSurface */));
 
         mSplitController.updateOverlayContainer(mTransaction, overlayContainer);
@@ -618,7 +618,8 @@
         final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
         final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
                 new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
-                true /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+                TASK_ID, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */);
         parentInfo.getConfiguration().windowConfiguration.getBounds().offset(10, 10);
 
         mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
@@ -642,7 +643,8 @@
         final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
         final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
                 new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
-                true /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+                TASK_ID, true /* visible */, false /* hasDirectActivity */,
+                null /* decorSurface */);
 
         mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index d852204..0512412 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -1164,7 +1164,7 @@
     public void testOnTransactionReady_taskFragmentParentInfoChanged() {
         final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
         final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                DEFAULT_DISPLAY, TASK_ID, true /* visible */, false /* hasDirectActivity */,
                 null /* decorSurface */);
         transaction.addChange(new TaskFragmentTransaction.Change(
                 TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
@@ -1625,7 +1625,7 @@
         final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
         final Configuration configuration = new Configuration();
         final TaskFragmentParentInfo originalInfo = new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                DEFAULT_DISPLAY, TASK_ID, true /* visible */, false /* hasDirectActivity */,
                 null /* decorSurface */);
         mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
                 TASK_ID, originalInfo);
@@ -1634,7 +1634,7 @@
         // Making a public configuration change while the Task is invisible.
         configuration.densityDpi += 100;
         final TaskFragmentParentInfo invisibleInfo = new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, false /* visible */, false /* hasDirectActivity */,
+                DEFAULT_DISPLAY, TASK_ID, false /* visible */, false /* hasDirectActivity */,
                 null /* decorSurface */);
         mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
                 TASK_ID, invisibleInfo);
@@ -1646,7 +1646,7 @@
 
         // Updates when Task to become visible
         final TaskFragmentParentInfo visibleInfo = new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                DEFAULT_DISPLAY, TASK_ID, true /* visible */, false /* hasDirectActivity */,
                 null /* decorSurface */);
         mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class),
                 TASK_ID, visibleInfo);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 2847232..97f4d07 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -23,6 +23,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static org.junit.Assert.assertEquals;
@@ -82,7 +83,7 @@
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                DEFAULT_DISPLAY, TASK_ID, true /* visible */, false /* hasDirectActivity */,
                 null /* decorSurface */));
 
         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
@@ -90,7 +91,7 @@
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                DEFAULT_DISPLAY, TASK_ID, true /* visible */, false /* hasDirectActivity */,
                 null /* decorSurface */));
 
         assertEquals(WINDOWING_MODE_FREEFORM,
@@ -111,14 +112,14 @@
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                DEFAULT_DISPLAY, TASK_ID, true /* visible */, false /* hasDirectActivity */,
                 null /* decorSurface */));
 
         assertFalse(taskContainer.isInPictureInPicture());
 
         configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
         taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
-                DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */,
+                DEFAULT_DISPLAY, TASK_ID, true /* visible */, false /* hasDirectActivity */,
                 null /* decorSurface */));
 
         assertTrue(taskContainer.isInPictureInPicture());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
new file mode 100644
index 0000000..a1e9f08
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 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 com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.Mockito.never;
+
+import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentOrganizer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+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 class for {@link TaskFragmentAnimationController}.
+ *
+ * Build/Install/Run:
+ *  atest WMJetpackUnitTests:TaskFragmentAnimationControllerTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TaskFragmentAnimationControllerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    @Mock
+    private TaskFragmentOrganizer mOrganizer;
+    private TaskFragmentAnimationController mAnimationController;
+
+    @Before
+    public void setup() {
+        mAnimationController = new TaskFragmentAnimationController(mOrganizer);
+    }
+
+    @Test
+    public void testRegisterRemoteAnimations() {
+        mAnimationController.registerRemoteAnimations();
+
+        verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
+
+        mAnimationController.registerRemoteAnimations();
+
+        // No extra call if it has been registered.
+        verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
+    }
+
+    @Test
+    public void testUnregisterRemoteAnimations() {
+        mAnimationController.unregisterRemoteAnimations();
+
+        // No call if it is not registered.
+        verify(mOrganizer, never()).unregisterRemoteAnimations();
+
+        mAnimationController.registerRemoteAnimations();
+        mAnimationController.unregisterRemoteAnimations();
+
+        verify(mOrganizer).unregisterRemoteAnimations();
+
+        mAnimationController.unregisterRemoteAnimations();
+
+        // No extra call if it has been unregistered.
+        verify(mOrganizer).unregisterRemoteAnimations();
+    }
+}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1a3aa8e..a79bc97 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -39,20 +39,6 @@
     path: "src",
 }
 
-// Sources that have no dependencies that can be used directly downstream of this library
-// TODO(b/322791067): move these sources to WindowManager-Shell-shared
-filegroup {
-    name: "wm_shell_util-sources",
-    srcs: [
-        "src/com/android/wm/shell/common/bubbles/*.kt",
-        "src/com/android/wm/shell/common/bubbles/*.java",
-        "src/com/android/wm/shell/common/desktopmode/*.kt",
-        "src/com/android/wm/shell/pip/PipContentOverlay.java",
-        "src/com/android/wm/shell/util/**/*.java",
-    ],
-    path: "src",
-}
-
 // Aidls which can be used directly downstream of this library
 filegroup {
     name: "wm_shell-aidls",
@@ -187,9 +173,11 @@
         ":wm_shell-shared-aidls",
     ],
     static_libs: [
+        "androidx.core_core-animation",
         "androidx.dynamicanimation_dynamicanimation",
         "jsr330",
     ],
+    kotlincflags: ["-Xjvm-default=all"],
 }
 
 java_library {
@@ -208,13 +196,13 @@
         // TODO(b/168581922) protologtool do not support kotlin(*.kt)
         ":wm_shell-sources-kt",
         ":wm_shell-aidls",
+        ":wm_shell-shared-aidls",
     ],
     resource_dirs: [
         "res",
     ],
     static_libs: [
         "androidx.appcompat_appcompat",
-        "androidx.core_core-animation",
         "androidx.core_core-ktx",
         "androidx.arch.core_core-runtime",
         "androidx.datastore_datastore",
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index bbbc23e..3b739c3 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -23,6 +23,7 @@
     <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
+    <uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
 
     <application>
         <activity
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 9de10c0..526ccd5 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -25,11 +25,10 @@
 }
 
 flag {
-    name: "enable_pip2_implementation"
+    name: "enable_pip2"
     namespace: "multitasking"
     description: "Enables the new implementation of PiP (PiP2)"
-    bug: "290220798"
-    is_fixed_read_only: true
+    bug: "311462191"
 }
 
 flag {
@@ -138,3 +137,17 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+   name: "enable_bubble_to_fullscreen"
+   namespace: "multitasking"
+   description: "Enable an option to move bubbles to fullscreen"
+   bug: "363326492"
+}
+
+flag {
+    name: "enable_flexible_split"
+    namespace: "multitasking"
+    description: "Enables flexibile split feature for split screen"
+    bug: "349828130"
+}
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt
index d35f493..f09969d 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt
@@ -16,7 +16,7 @@
 package com.android.wm.shell.bubbles
 
 import android.view.LayoutInflater
-import com.android.wm.shell.common.bubbles.BubblePopupView
+import com.android.wm.shell.shared.bubbles.BubblePopupView
 import com.android.wm.shell.testing.goldenpathmanager.WMShellGoldenPathManager
 import com.android.wm.shell.R
 import org.junit.Rule
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 4b97451..b38d00da 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -30,7 +30,7 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors.directExecutor
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index faadf1d..96ffa03 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -53,7 +53,7 @@
 import org.mockito.kotlin.mock
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit
 import java.util.function.Consumer
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index 935d129..ecb2b25 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -31,12 +31,12 @@
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.BubblePositioner
 import com.android.wm.shell.bubbles.DeviceConfig
-import com.android.wm.shell.common.bubbles.BaseBubblePinController
-import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
-import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
+import com.android.wm.shell.shared.bubbles.BaseBubblePinController
+import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
+import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.LEFT
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.RIGHT
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
index a0a06f1..806d026 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.wm.shell.common.bubbles.BubblePopupView
+<com.android.wm.shell.shared.bubbles.BubblePopupView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
@@ -53,4 +53,4 @@
         android:textAlignment="center"
         android:text="@string/bubble_bar_education_manage_text"/>
 
-</com.android.wm.shell.common.bubbles.BubblePopupView>
\ No newline at end of file
+</com.android.wm.shell.shared.bubbles.BubblePopupView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
index b489a5c..7fa586c 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.wm.shell.common.bubbles.BubblePopupView
+<com.android.wm.shell.shared.bubbles.BubblePopupView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
@@ -53,4 +53,4 @@
         android:textAlignment="center"
         android:text="@string/bubble_bar_education_stack_text"/>
 
-</com.android.wm.shell.common.bubbles.BubblePopupView>
\ No newline at end of file
+</com.android.wm.shell.shared.bubbles.BubblePopupView>
\ No newline at end of file
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/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 36d0a3c..a353db7 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -155,6 +155,8 @@
     <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
     <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] -->
     <string name="bubble_dismiss_text">Dismiss bubble</string>
+    <!-- Text used to move the bubble to fullscreen. [CHAR LIMIT=30] -->
+    <string name="bubble_fullscreen_text">Move to fullscreen</string>
     <!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]-->
     <string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string>
     <!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
similarity index 88%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
copy to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
index 3c5beeb..e21bf8f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.wm.shell.shared;
 
-parcelable BubbleBarLocation;
\ No newline at end of file
+parcelable GroupedRecentTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
index a2d2b9a..65e079e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared;
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
@@ -25,6 +25,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.shared.split.SplitBounds;
+
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
similarity index 96%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
index eec2468..7086691 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles
+package com.android.wm.shell.shared.bubbles
 
 import android.graphics.Point
 import android.graphics.RectF
@@ -23,9 +23,9 @@
 import androidx.core.animation.Animator
 import androidx.core.animation.AnimatorListenerAdapter
 import androidx.core.animation.ObjectAnimator
-import com.android.wm.shell.common.bubbles.BaseBubblePinController.LocationChangeListener
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
+import com.android.wm.shell.shared.bubbles.BaseBubblePinController.LocationChangeListener
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.LEFT
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.RIGHT
 
 /**
  * Base class for common logic shared between different bubble views to support pinning bubble bar
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.aidl
similarity index 93%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.aidl
index 3c5beeb..4fe7611 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.wm.shell.shared.bubbles;
 
 parcelable BubbleBarLocation;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
index f0bdfde..191875d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.common.bubbles
+package com.android.wm.shell.shared.bubbles
 
 import android.os.Parcel
 import android.os.Parcelable
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarUpdate.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarUpdate.java
index ec3c601..5bde1e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarUpdate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.wm.shell.shared.bubbles;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleConstants.java
similarity index 89%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleConstants.java
index 0329b8d..3396bc4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleConstants.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.wm.shell.shared.bubbles;
 
 /**
  * Constants shared between bubbles in shell & things we have to do for bubbles in launcher.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java
similarity index 92%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java
index 829af08..5876682 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.wm.shell.shared.bubbles;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -48,10 +48,11 @@
     @Nullable
     private String mAppName;
     private boolean mIsImportantConversation;
+    private boolean mShowAppBadge;
 
     public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon,
             int userId, String packageName, @Nullable String title, @Nullable String appName,
-            boolean isImportantConversation) {
+            boolean isImportantConversation, boolean showAppBadge) {
         mKey = key;
         mFlags = flags;
         mShortcutId = shortcutId;
@@ -61,6 +62,7 @@
         mTitle = title;
         mAppName = appName;
         mIsImportantConversation = isImportantConversation;
+        mShowAppBadge = showAppBadge;
     }
 
     private BubbleInfo(Parcel source) {
@@ -73,6 +75,7 @@
         mTitle = source.readString();
         mAppName = source.readString();
         mIsImportantConversation = source.readBoolean();
+        mShowAppBadge = source.readBoolean();
     }
 
     public String getKey() {
@@ -115,6 +118,10 @@
         return mIsImportantConversation;
     }
 
+    public boolean showAppBadge() {
+        return mShowAppBadge;
+    }
+
     /**
      * Whether this bubble is currently being hidden from the stack.
      */
@@ -172,6 +179,7 @@
         parcel.writeString(mTitle);
         parcel.writeString(mAppName);
         parcel.writeBoolean(mIsImportantConversation);
+        parcel.writeBoolean(mShowAppBadge);
     }
 
     @NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupDrawable.kt
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupDrawable.kt
index 887af17..8681acf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupDrawable.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.common.bubbles
+package com.android.wm.shell.shared.bubbles
 
 import android.annotation.ColorInt
 import android.graphics.Canvas
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupView.kt
similarity index 95%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupView.kt
index 444fbf7..802d7d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupView.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.common.bubbles
+package com.android.wm.shell.shared.bubbles
 
 import android.content.Context
 import android.graphics.Rect
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissCircleView.java
similarity index 96%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissCircleView.java
index 7c5bb21..0c05156 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissCircleView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.wm.shell.shared.bubbles;
 
 import android.content.Context;
 import android.content.res.Configuration;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissView.kt
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissView.kt
index e06de9e..2bb66b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissView.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles
+package com.android.wm.shell.shared.bubbles
 
 import android.animation.ObjectAnimator
 import android.content.Context
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/OWNERS
similarity index 100%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/OWNERS
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RelativeTouchListener.kt
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RelativeTouchListener.kt
index 4e55ba2..b1f4e33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RelativeTouchListener.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles
+package com.android.wm.shell.shared.bubbles
 
 import android.graphics.PointF
 import android.view.MotionEvent
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RemovedBubble.java
similarity index 94%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RemovedBubble.java
index f90591b..c83696c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RemovedBubble.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.wm.shell.shared.bubbles;
 
 import android.annotation.NonNull;
 import android.os.Parcel;
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 434885f..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
@@ -22,10 +22,14 @@
 import com.android.window.flags.Flags
 
 /*
- * A shared class to check desktop mode flags state.
+ * An enum to check desktop mode flags state.
  *
- * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag
- * value and the developer option override state (if applicable).
+ * This enum provides a centralized way to control the behavior of flags related to desktop
+ * windowing features which are aiming for developer preview before their release. It allows
+ * developer option to override the default behavior of these flags.
+ *
+ * NOTE: Flags should only be added to this enum when they have received Product and UX
+ * alignment that the feature is ready for developer preview, otherwise just do a flag check.
  */
 enum class DesktopModeFlags(
     // Function called to obtain aconfig flag value.
@@ -44,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/common/desktopmode/DesktopModeTransitionSource.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
similarity index 92%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
index c968e80..f7ddf71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.desktopmode;
+package com.android.wm.shell.shared.desktopmode;
 
 parcelable DesktopModeTransitionSource;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
index dbbf1786..d15fbed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/desktopmode/DesktopModeTransitionSource.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeTransitionSource.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.desktopmode
+package com.android.wm.shell.shared.desktopmode
 
 import android.os.Parcel
 import android.os.Parcelable
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
index 9999f08..b92b8ef 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/handles/RegionSamplingHelper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.navigationbar;
+package com.android.wm.shell.shared.handles;
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -194,7 +194,7 @@
             ViewRootImpl viewRootImpl = mSampledView.getViewRootImpl();
             SurfaceControl stopLayerControl = null;
             if (viewRootImpl != null) {
-                 stopLayerControl = viewRootImpl.getSurfaceControl();
+                stopLayerControl = viewRootImpl.getSurfaceControl();
             }
             if (stopLayerControl == null || !stopLayerControl.isValid()) {
                 if (!mWaitingOnDraw) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
index ff2d46e..cf39415 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip;
+package com.android.wm.shell.shared.pip;
 
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
index 88b7528..7c1faa6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitBounds.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.util;
+package com.android.wm.shell.shared.split;
 
 import android.graphics.Rect;
 import android.os.Parcel;
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..d425041 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
@@ -74,6 +74,7 @@
 import android.window.IOnBackInvokedCallback;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -1066,14 +1067,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 +1102,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();
                                 });
                     }
 
@@ -1172,16 +1180,18 @@
         }
 
         private void applyFinishOpenTransition() {
-            if (mFinishOpenTransaction != null) {
-                mFinishOpenTransaction.apply();
-                mFinishOpenTransaction = null;
-            }
-            if (mFinishOpenTransitionCallback != null) {
-                mFinishOpenTransitionCallback.onTransitionFinished(null);
-                mFinishOpenTransitionCallback = null;
-            }
             mOpenTransitionInfo = null;
             mPrepareOpenTransition = null;
+            if (mFinishOpenTransaction != null) {
+                final SurfaceControl.Transaction t = mFinishOpenTransaction;
+                mFinishOpenTransaction = null;
+                t.apply();
+            }
+            if (mFinishOpenTransitionCallback != null) {
+                final Transitions.TransitionFinishCallback callback = mFinishOpenTransitionCallback;
+                mFinishOpenTransitionCallback = null;
+                callback.onTransitionFinished(null);
+            }
         }
 
         private void applyAndFinish(@NonNull SurfaceControl.Transaction st,
@@ -1199,6 +1209,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) {
@@ -1262,19 +1275,24 @@
             ComponentName openComponent = null;
             int tmpSize;
             int openTaskId = INVALID_TASK_ID;
+            WindowContainerToken openToken = null;
             for (int j = init.getChanges().size() - 1; j >= 0; --j) {
                 final TransitionInfo.Change change = init.getChanges().get(j);
                 if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
                     openComponent = findComponentName(change);
                     openTaskId = findTaskId(change);
+                    openToken = findToken(change);
                     if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
                         openShowWallpaper = true;
                     }
                     break;
                 }
             }
-            if (openComponent == null && openTaskId == INVALID_TASK_ID) {
-                // shouldn't happen.
+            if (openComponent == null && openTaskId == INVALID_TASK_ID && openToken == null) {
+                // This shouldn't happen, but if that happen, consume the initial transition anyway.
+                Log.e(TAG, "Unable to merge following transition, cannot find the gesture "
+                        + "animated target from the open transition=" + mOpenTransitionInfo);
+                mOpenTransitionInfo = null;
                 return;
             }
             // find first non-prepare open target
@@ -1305,7 +1323,7 @@
                 boolean moveToTop = false;
                 for (int j = info.getChanges().size() - 1; j >= 0; --j) {
                     final TransitionInfo.Change change = info.getChanges().get(j);
-                    if (isSameChangeTarget(openComponent, openTaskId, change)) {
+                    if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
                         moveToTop = change.hasFlags(FLAG_MOVED_TO_TOP);
                         info.getChanges().remove(j);
                     } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))
@@ -1318,8 +1336,11 @@
                     tmpSize = init.getChanges().size();
                     for (int i = 0; i < tmpSize; ++i) {
                         final TransitionInfo.Change change = init.getChanges().get(i);
+                        if (change.hasFlags(FLAG_IS_WALLPAPER)) {
+                            continue;
+                        }
                         if (moveToTop) {
-                            if (isSameChangeTarget(openComponent, openTaskId, change)) {
+                            if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
                                 change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
                             }
                         }
@@ -1348,7 +1369,7 @@
                 if (nonBackClose && nonBackOpen) {
                     for (int j = info.getChanges().size() - 1; j >= 0; --j) {
                         final TransitionInfo.Change change = info.getChanges().get(j);
-                        if (isSameChangeTarget(openComponent, openTaskId, change)) {
+                        if (isSameChangeTarget(openComponent, openTaskId, openToken, change)) {
                             info.getChanges().remove(j);
                         } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
                             info.getChanges().remove(j);
@@ -1358,6 +1379,8 @@
             }
             ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation transition, merge pending "
                     + "transitions result=%s", info);
+            // Only handle one merge transition request.
+            mOpenTransitionInfo = null;
         }
 
         @Override
@@ -1368,7 +1391,9 @@
                 mClosePrepareTransition = null;
             }
             // try to handle unexpected transition
-            mergePendingTransitions(info);
+            if (mOpenTransitionInfo != null) {
+                mergePendingTransitions(info);
+            }
 
             if (isNotGestureBackTransition(info) || shouldCancelAnimation(info)
                     || !mCloseTransitionRequested) {
@@ -1382,6 +1407,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) {
@@ -1617,6 +1643,10 @@
         return false;
     }
 
+    private static WindowContainerToken findToken(TransitionInfo.Change change) {
+        return change.getContainer();
+    }
+
     private static ComponentName findComponentName(TransitionInfo.Change change) {
         final ComponentName componentName = change.getActivityComponent();
         if (componentName != null) {
@@ -1638,11 +1668,13 @@
     }
 
     private static boolean isSameChangeTarget(ComponentName topActivity, int taskId,
-            TransitionInfo.Change change) {
+            WindowContainerToken token, TransitionInfo.Change change) {
         final ComponentName openChange = findComponentName(change);
         final int firstTaskId = findTaskId(change);
+        final WindowContainerToken openToken = findToken(change);
         return (openChange != null && openChange == topActivity)
-                || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId);
+                || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId)
+                || (openToken != null && token == openToken);
     }
 
     private static boolean canBeTransitionTarget(TransitionInfo.Change change) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 3e758bb..0c95934 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -53,9 +53,9 @@
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
-import com.android.wm.shell.common.bubbles.BubbleInfo;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.bubbles.BubbleInfo;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -349,7 +349,8 @@
                 getPackageName(),
                 getTitle(),
                 getAppName(),
-                isImportantConversation());
+                isImportantConversation(),
+                !isAppLaunchIntent());
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3dc33c2..c545d73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -104,14 +104,14 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
-import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -1225,7 +1225,7 @@
         mBubblePositioner.setBubbleBarLocation(location);
         mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen);
         if (mBubbleData.getSelectedBubble() != null) {
-            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+            showExpandedViewForBubbleBar();
         }
     }
 
@@ -1243,7 +1243,7 @@
         }
         if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
             // We did not remove the selected bubble. Expand it again
-            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+            showExpandedViewForBubbleBar();
         }
     }
 
@@ -1997,15 +1997,10 @@
 
         @Override
         public void expansionChanged(boolean isExpanded) {
-            if (mLayerView != null) {
-                if (!isExpanded) {
-                    mLayerView.collapse();
-                } else {
-                    BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
-                    if (selectedBubble != null) {
-                        mLayerView.showExpandedView(selectedBubble);
-                    }
-                }
+            // in bubble bar mode, let the request to show the expanded view come from launcher.
+            // only collapse here if we're collapsing.
+            if (mLayerView != null && !isExpanded) {
+                mLayerView.collapse();
             }
         }
 
@@ -2151,6 +2146,13 @@
         }
     };
 
+    private void showExpandedViewForBubbleBar() {
+        BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+        if (selectedBubble != null && mLayerView != null) {
+            mLayerView.showExpandedView(selectedBubble);
+        }
+    }
+
     private void updateOverflowButtonDot() {
         BubbleOverflow overflow = mBubbleData.getOverflow();
         if (overflow == null) return;
@@ -2532,6 +2534,15 @@
                 if (mLayerView != null) mLayerView.updateExpandedView();
             });
         }
+
+        @Override
+        public void showExpandedView() {
+            mMainExecutor.execute(() -> {
+                if (mLayerView != null) {
+                    showExpandedViewForBubbleBar();
+                }
+            });
+        }
     }
 
     private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 4ad1802..709a7bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -41,10 +41,10 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubbles.DismissReason;
-import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
-import com.android.wm.shell.common.bubbles.RemovedBubble;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.shared.bubbles.RemovedBubble;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
index 4e80e90..ec4854b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.bubbles
 
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 
 /** Manager interface for bubble expanded views. */
 interface BubbleExpandedViewManager {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt
index bdb09e1..fd110a276 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt
@@ -17,8 +17,8 @@
 
 import android.graphics.Color
 import com.android.wm.shell.R
-import com.android.wm.shell.common.bubbles.BubblePopupDrawable
-import com.android.wm.shell.common.bubbles.BubblePopupView
+import com.android.wm.shell.shared.bubbles.BubblePopupDrawable
+import com.android.wm.shell.shared.bubbles.BubblePopupView
 
 /**
  * A convenience method to setup the [BubblePopupView] with the correct config using local resources
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 0cf187b..c386c93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -32,7 +32,7 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
 /**
  * Keeps track of display size, configuration, and specific bubble sizes. One place for all
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 53bbf88..2795881 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -24,10 +24,10 @@
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
 import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.LEFT;
 import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.RIGHT;
-import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
 import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
+import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -91,8 +91,8 @@
 import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.bubbles.DismissView;
-import com.android.wm.shell.common.bubbles.RelativeTouchListener;
+import com.android.wm.shell.shared.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
 import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 9a27fb6..62895fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -38,9 +38,9 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
-import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
index 48692d4..00a8172 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt
@@ -18,7 +18,7 @@
 package com.android.wm.shell.bubbles
 
 import com.android.wm.shell.R
-import com.android.wm.shell.common.bubbles.DismissView
+import com.android.wm.shell.shared.bubbles.DismissView
 
 fun DismissView.setup() {
     setup(DismissView.Config(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5c78974..1855b93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -20,7 +20,7 @@
 import android.graphics.Rect;
 import android.content.pm.ShortcutInfo;
 import com.android.wm.shell.bubbles.IBubblesListener;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
 /**
  * Interface that is exposed to remote callers (launcher) to manipulate the bubbles feature when
@@ -53,4 +53,6 @@
     oneway void showShortcutBubble(in ShortcutInfo info) = 12;
 
     oneway void showAppBubble(in Intent intent) = 13;
+
+    oneway void showExpandedView() = 14;
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
index 14d29cd..eb907db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -17,7 +17,7 @@
 package com.android.wm.shell.bubbles;
 
 import android.os.Bundle;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 /**
  * Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
  */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 6d868d2..f90b2aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -46,7 +46,7 @@
 import com.android.wm.shell.bubbles.BubbleTaskView;
 import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
 import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 import com.android.wm.shell.taskview.TaskView;
 
 import java.util.function.Supplier;
@@ -228,6 +228,13 @@
             public void onDismissBubble(Bubble bubble) {
                 mManager.dismissBubble(bubble, Bubbles.DISMISS_USER_GESTURE);
             }
+
+            @Override
+            public void onMoveToFullscreen(Bubble bubble) {
+                if (mTaskView != null) {
+                    mTaskView.moveToFullscreen();
+                }
+            }
         });
         mHandleView.setOnClickListener(view -> {
             mMenuViewController.showMenu(true /* animated */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index eeb5c94..07463bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -20,8 +20,8 @@
 import android.view.MotionEvent
 import android.view.View
 import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.common.bubbles.DismissView
-import com.android.wm.shell.common.bubbles.RelativeTouchListener
+import com.android.wm.shell.shared.bubbles.DismissView
+import com.android.wm.shell.shared.bubbles.RelativeTouchListener
 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject
 
 /** Controller for handling drag interactions with [BubbleBarExpandedView] */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index ac42453..1c9c195 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -44,9 +44,9 @@
 import com.android.wm.shell.bubbles.DeviceConfig;
 import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
-import com.android.wm.shell.common.bubbles.BaseBubblePinController;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
-import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.BaseBubblePinController;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.DismissView;
 
 import kotlin.Unit;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index 0d72998..5148107 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -29,6 +29,7 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
@@ -219,6 +220,21 @@
                 }
         ));
 
+        if (Flags.enableBubbleAnything() || Flags.enableBubbleToFullscreen()) {
+            menuActions.add(new BubbleBarMenuView.MenuAction(
+                    Icon.createWithResource(resources,
+                            R.drawable.desktop_mode_ic_handle_menu_fullscreen),
+                    resources.getString(R.string.bubble_fullscreen_text),
+                    tintColor,
+                    view -> {
+                        hideMenu(true /* animated */);
+                        if (mListener != null) {
+                            mListener.onMoveToFullscreen(bubble);
+                        }
+                    }
+            ));
+        }
+
         return menuActions;
     }
 
@@ -249,5 +265,10 @@
          * Dismiss bubble and remove it from the bubble stack
          */
         void onDismissBubble(Bubble bubble);
+
+        /**
+         * Move the bubble to fullscreen.
+         */
+        void onMoveToFullscreen(Bubble bubble);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index e108f7b..9fd255d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -34,9 +34,9 @@
 import com.android.wm.shell.bubbles.BubbleEducationController
 import com.android.wm.shell.bubbles.BubbleViewProvider
 import com.android.wm.shell.bubbles.setup
-import com.android.wm.shell.common.bubbles.BubblePopupDrawable
-import com.android.wm.shell.common.bubbles.BubblePopupView
 import com.android.wm.shell.shared.animation.PhysicsAnimator
+import com.android.wm.shell.shared.bubbles.BubblePopupDrawable
+import com.android.wm.shell.shared.bubbles.BubblePopupView
 import kotlin.math.roundToInt
 
 /** Manages bubble education presentation and animation */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
index 651bf02..23ba2bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
@@ -25,8 +25,8 @@
 import androidx.core.view.updateLayoutParams
 import com.android.wm.shell.R
 import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.common.bubbles.BaseBubblePinController
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BaseBubblePinController
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 
 /**
  * Controller to manage pinning bubble bar to left or right when dragging starts from the bubble bar
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 5b01a0d..f03daad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -291,17 +291,11 @@
             if (hadImeSourceControl != hasImeSourceControl) {
                 dispatchImeControlTargetChanged(mDisplayId, hasImeSourceControl);
             }
+            final boolean hasImeLeash = hasImeSourceControl && imeSourceControl.getLeash() != null;
 
             boolean pendingImeStartAnimation = false;
-            boolean canAnimate;
-            if (android.view.inputmethod.Flags.refactorInsetsController()) {
-                canAnimate = hasImeSourceControl && imeSourceControl.getLeash() != null;
-            } else {
-                canAnimate = hasImeSourceControl;
-            }
-
             boolean positionChanged = false;
-            if (canAnimate) {
+            if (hasImeLeash) {
                 if (mAnimation != null) {
                     final Point lastSurfacePosition = hadImeSourceControl
                             ? mImeSourceControl.getSurfacePosition() : null;
@@ -325,6 +319,13 @@
                 // continue the bar to slide to the end (even without visible IME)
                 mAnimation.cancel();
             }
+
+            // Make mImeSourceControl point to the new control before starting the animation.
+            if (hadImeSourceControl && mImeSourceControl != imeSourceControl) {
+                mImeSourceControl.release(SurfaceControl::release);
+            }
+            mImeSourceControl = imeSourceControl;
+
             if (positionChanged) {
                 if (android.view.inputmethod.Flags.refactorInsetsController()) {
                     // For showing the IME, the leash has to be available first. Hiding
@@ -338,11 +339,6 @@
                 }
             }
 
-            if (hadImeSourceControl && mImeSourceControl != imeSourceControl) {
-                mImeSourceControl.release(SurfaceControl::release);
-            }
-            mImeSourceControl = imeSourceControl;
-
             if (android.view.inputmethod.Flags.refactorInsetsController()) {
                 if (pendingImeStartAnimation) {
                     startAnimation(true, true /* forceRestart */);
@@ -465,11 +461,12 @@
 
         private void startAnimation(final boolean show, final boolean forceRestart,
                 @NonNull final ImeTracker.Token statsToken) {
+            if (mImeSourceControl == null || mImeSourceControl.getLeash() == null) {
+                if (DEBUG) Slog.d(TAG, "No leash available, not starting the animation.");
+                return;
+            }
             if (android.view.inputmethod.Flags.refactorInsetsController()) {
-                if (mImeSourceControl == null || mImeSourceControl.getLeash() == null) {
-                    if (DEBUG) Slog.d(TAG, "No leash available, not starting the animation.");
-                    return;
-                } else if (!mImeRequestedVisible && show) {
+                if (!mImeRequestedVisible && show) {
                     // we have a control with leash, but the IME was not requested visible before,
                     // therefore aborting the show animation.
                     Slog.e(TAG, "IME was not requested visible, not starting the show animation.");
@@ -478,7 +475,7 @@
                 }
             }
             final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
-            if (imeSource == null || mImeSourceControl == null) {
+            if (imeSource == null) {
                 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
             }
@@ -515,8 +512,10 @@
                 }
                 mAnimation.cancel();
             }
-            final float defaultY = mImeSourceControl.getSurfacePosition().y;
-            final float x = mImeSourceControl.getSurfacePosition().x;
+            final InsetsSourceControl animatingControl = new InsetsSourceControl(mImeSourceControl);
+            final SurfaceControl animatingLeash = animatingControl.getLeash();
+            final float defaultY = animatingControl.getSurfacePosition().y;
+            final float x = animatingControl.getSurfacePosition().x;
             final float hiddenY = defaultY + mImeFrame.height();
             final float shownY = defaultY;
             final float startY = show ? hiddenY : shownY;
@@ -538,13 +537,10 @@
             mAnimation.addUpdateListener(animation -> {
                 SurfaceControl.Transaction t = mTransactionPool.acquire();
                 float value = (float) animation.getAnimatedValue();
-                if (!android.view.inputmethod.Flags.refactorInsetsController() || (
-                        mImeSourceControl != null && mImeSourceControl.getLeash() != null)) {
-                    t.setPosition(mImeSourceControl.getLeash(), x, value);
-                    final float alpha = (mAnimateAlpha || isFloating)
-                            ? (value - hiddenY) / (shownY - hiddenY) : 1.f;
-                    t.setAlpha(mImeSourceControl.getLeash(), alpha);
-                }
+                t.setPosition(animatingLeash, x, value);
+                final float alpha = (mAnimateAlpha || isFloating)
+                        ? (value - hiddenY) / (shownY - hiddenY) : 1f;
+                t.setAlpha(animatingLeash, alpha);
                 dispatchPositionChanged(mDisplayId, imeTop(value), t);
                 t.apply();
                 mTransactionPool.release(t);
@@ -561,7 +557,7 @@
                     ValueAnimator valueAnimator = (ValueAnimator) animation;
                     float value = (float) valueAnimator.getAnimatedValue();
                     SurfaceControl.Transaction t = mTransactionPool.acquire();
-                    t.setPosition(mImeSourceControl.getLeash(), x, value);
+                    t.setPosition(animatingLeash, x, value);
                     if (DEBUG) {
                         Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
                                 + imeTop(hiddenY) + "->" + imeTop(shownY)
@@ -573,19 +569,19 @@
                     final float alpha = (mAnimateAlpha || isFloating)
                             ? (value - hiddenY) / (shownY - hiddenY)
                             : 1.f;
-                    t.setAlpha(mImeSourceControl.getLeash(), alpha);
+                    t.setAlpha(animatingLeash, alpha);
                     if (mAnimationDirection == DIRECTION_SHOW) {
                         ImeTracker.forLogging().onProgress(mStatsToken,
                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
-                        t.show(mImeSourceControl.getLeash());
+                        t.show(animatingLeash);
                     }
                     if (DEBUG_IME_VISIBILITY) {
                         EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START,
                                 mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
                                 mDisplayId, mAnimationDirection, alpha, value, endY,
-                                Objects.toString(mImeSourceControl.getLeash()),
-                                Objects.toString(mImeSourceControl.getInsetsHint()),
-                                Objects.toString(mImeSourceControl.getSurfacePosition()),
+                                Objects.toString(animatingLeash),
+                                Objects.toString(animatingControl.getInsetsHint()),
+                                Objects.toString(animatingControl.getSurfacePosition()),
                                 Objects.toString(mImeFrame));
                     }
                     t.apply();
@@ -599,31 +595,23 @@
                         EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL,
                                 mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
                                 mDisplayId,
-                                Objects.toString(mImeSourceControl.getInsetsHint()));
+                                Objects.toString(animatingControl.getInsetsHint()));
                     }
                 }
 
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    boolean hasLeash =
-                            mImeSourceControl != null && mImeSourceControl.getLeash() != null;
                     if (DEBUG) Slog.d(TAG, "onAnimationEnd " + mCancelled);
                     SurfaceControl.Transaction t = mTransactionPool.acquire();
                     if (!mCancelled) {
-                        if (!android.view.inputmethod.Flags.refactorInsetsController()
-                                || hasLeash) {
-                            t.setPosition(mImeSourceControl.getLeash(), x, endY);
-                            t.setAlpha(mImeSourceControl.getLeash(), 1.f);
-                        }
+                        t.setPosition(animatingLeash, x, endY);
+                        t.setAlpha(animatingLeash, 1.f);
                     }
                     dispatchEndPositioning(mDisplayId, mCancelled, t);
                     if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
                         ImeTracker.forLogging().onProgress(mStatsToken,
                                 ImeTracker.PHASE_WM_ANIMATION_RUNNING);
-                        if (!android.view.inputmethod.Flags.refactorInsetsController()
-                                || hasLeash) {
-                            t.hide(mImeSourceControl.getLeash());
-                        }
+                        t.hide(animatingLeash);
                         removeImeSurface(mDisplayId);
                         ImeTracker.forLogging().onHidden(mStatsToken);
                     } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
@@ -636,13 +624,9 @@
                         EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END,
                                 mStatsToken != null ? mStatsToken.getTag() : ImeTracker.TOKEN_NONE,
                                 mDisplayId, mAnimationDirection, endY,
-                                Objects.toString(
-                                        mImeSourceControl != null ? mImeSourceControl.getLeash()
-                                                : "null"),
-                                Objects.toString(mImeSourceControl != null
-                                        ? mImeSourceControl.getInsetsHint() : "null"),
-                                Objects.toString(mImeSourceControl != null
-                                        ? mImeSourceControl.getSurfacePosition() : "null"),
+                                Objects.toString(animatingLeash),
+                                Objects.toString(animatingControl.getInsetsHint()),
+                                Objects.toString(animatingControl.getSurfacePosition()),
                                 Objects.toString(mImeFrame));
                     }
                     t.apply();
@@ -650,6 +634,7 @@
 
                     mAnimationDirection = DIRECTION_NONE;
                     mAnimation = null;
+                    animatingControl.release(SurfaceControl::release);
                 }
             });
             if (!show) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
index fad3dee..1929729 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
@@ -42,6 +42,7 @@
                     .setSourceCrop(crop)
                     .setCaptureSecureLayers(true)
                     .setAllowProtected(true)
+                    .setHintForSeamlessTransition(true)
                     .build()));
     }
 
@@ -78,6 +79,9 @@
             mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace());
             mTransaction.reparent(mScreenshot, mParentSurfaceControl);
             mTransaction.setLayer(mScreenshot, mLayer);
+            if (buffer.containsHdrLayers()) {
+                mTransaction.setDimmingEnabled(mScreenshot, false);
+            }
             mTransaction.show(mScreenshot);
             mTransaction.apply();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index dcf84d9..7070ce9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -24,7 +24,6 @@
 import android.content.pm.PackageManager
 import android.graphics.Rect
 import android.os.RemoteException
-import android.os.SystemProperties
 import android.util.DisplayMetrics
 import android.util.Log
 import android.util.Pair
@@ -178,9 +177,7 @@
                 "org.chromium.arc", 0)
             val isTv = AppGlobals.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK, 0)
-            isPip2ExperimentEnabled = SystemProperties.getBoolean(
-                    "persist.wm_shell.pip2", false) ||
-                    (Flags.enablePip2Implementation() && !isArc && !isTv)
+            isPip2ExperimentEnabled = Flags.enablePip2() && !isArc && !isTv
         }
         return isPip2ExperimentEnabled as Boolean
     }
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..6146ecd 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();
@@ -822,7 +831,11 @@
      */
     static class CompatUIHintsState {
         boolean mHasShownSizeCompatHint;
-        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/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 8ce7837..17869e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -19,8 +19,6 @@
 import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
 
-import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
@@ -40,6 +38,7 @@
 import com.android.wm.shell.compatui.api.CompatUIEvent;
 import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 import java.util.function.Consumer;
 
@@ -83,7 +82,7 @@
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mCallback = callback;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
-        if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+        if (DesktopModeStatus.canEnterDesktopMode(context)
                 && DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
             // Don't show the SCM button for freeform tasks
             mHasSizeCompat &= !taskInfo.isFreeform();
@@ -139,7 +138,7 @@
             boolean canShow) {
         final boolean prevHasSizeCompat = mHasSizeCompat;
         mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat();
-        if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
+        if (DesktopModeStatus.canEnterDesktopMode(mContext)
                 && DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)) {
             // Don't show the SCM button for freeform tasks
             mHasSizeCompat &= !taskInfo.isFreeform();
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..4adea23 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
@@ -147,7 +148,11 @@
  * dependencies that are device/form factor SystemUI implementation specific should go into their
  * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
  */
-@Module(includes = WMShellConcurrencyModule.class)
+@Module(
+        includes = {
+                WMShellConcurrencyModule.class,
+                WMShellCoroutinesModule.class
+        })
 public abstract class WMShellBaseModule {
 
     //
@@ -261,6 +266,7 @@
             Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
             Lazy<AccessibilityManager> accessibilityManager,
             CompatUIRepository compatUIRepository,
+            Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @NonNull CompatUIState compatUIState,
             @NonNull CompatUIComponentIdGenerator componentIdGenerator,
             @NonNull CompatUIComponentFactory compatUIComponentFactory,
@@ -273,6 +279,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 +298,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
new file mode 100644
index 0000000..cc47dbb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellCoroutinesModule.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.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
+import dagger.Module
+import dagger.Provides
+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 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 mainHandler: Handler
+  ): MainCoroutineDispatcher = mainHandler.asCoroutineDispatcher()
+
+  @Provides
+  @ShellBackgroundThread
+  fun provideBackgroundDispatcher(
+      @ShellBackgroundThread backgroundHandler: Handler
+  ): MainCoroutineDispatcher = backgroundHandler.asCoroutineDispatcher()
+
+  @Provides
+  @WMSingleton
+  @ShellMainThread
+  fun provideApplicationScope(
+      @ShellMainThread applicationDispatcher: MainCoroutineDispatcher,
+  ): CoroutineScope = CoroutineScope(applicationDispatcher)
+
+  @Provides
+  @WMSingleton
+  @ShellBackgroundThread
+  fun provideBackgroundCoroutineScope(
+      @ShellBackgroundThread backgroundDispatcher: MainCoroutineDispatcher,
+  ): CoroutineScope = CoroutineScope(backgroundDispatcher)
+
+  @Provides
+  @WMSingleton
+  @ShellBackgroundThread
+  fun provideBackgroundCoroutineContext(
+      @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..02ecfd9 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;
@@ -72,6 +73,7 @@
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator;
 import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
+import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
 import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
 import com.android.wm.shell.draganddrop.DragAndDropController;
@@ -117,6 +119,8 @@
 import dagger.Module;
 import dagger.Provides;
 
+import kotlinx.coroutines.CoroutineScope;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
@@ -237,7 +241,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 +264,8 @@
                     interactionJankMonitor,
                     genericLinksParser,
                     multiInstanceHelper,
-                    desktopTasksLimiter);
+                    desktopTasksLimiter,
+                    desktopActivityOrientationHandler);
         }
         return new CaptionWindowDecorViewModel(
                 context,
@@ -677,6 +683,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,
@@ -722,6 +746,17 @@
         return new AppHandleEducationFilter(context, appHandleEducationDatastoreRepository);
     }
 
+    @WMSingleton
+    @Provides
+    static AppHandleEducationController provideAppHandleEducationController(
+            AppHandleEducationFilter appHandleEducationFilter,
+            ShellTaskOrganizer shellTaskOrganizer,
+            AppHandleEducationDatastoreRepository appHandleEducationDatastoreRepository,
+            @ShellMainThread CoroutineScope applicationScope) {
+        return new AppHandleEducationController(appHandleEducationFilter,
+                shellTaskOrganizer, appHandleEducationDatastoreRepository, applicationScope);
+    }
+
     //
     // Drag and drop
     //
@@ -763,7 +798,8 @@
     @Provides
     static Object provideIndependentShellComponentsToCreate(
             DragAndDropController dragAndDropController,
-            Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional
+            Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
+            AppHandleEducationController appHandleEducationController
     ) {
         return new Object();
     }
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/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 31c8f1e..cca7500 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -18,8 +18,8 @@
 
 import android.graphics.Region;
 
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index b6f2a25..02cbe01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -132,7 +132,8 @@
             sessionId,
             taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
             taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
-
+            /* visible_task_count */
+            taskUpdate.visibleTaskCount
         )
     }
 
@@ -159,6 +160,7 @@
             val taskY: Int,
             val minimizeReason: MinimizeReason? = null,
             val unminimizeReason: UnminimizeReason? = null,
+            val visibleTaskCount: Int,
         )
 
         // Default value used when the task was not minimized.
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 2e36ffe..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
@@ -295,7 +294,8 @@
         postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
     ) {
         postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
-            val currentTaskUpdate = buildTaskUpdateForTask(taskInfo)
+            val currentTaskUpdate = buildTaskUpdateForTask(taskInfo,
+                postTransitionVisibleFreeformTasks.size())
             val previousTaskInfo = preTransitionVisibleFreeformTasks[taskId]
             when {
                 // new tasks added
@@ -306,28 +306,34 @@
                         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.
-                buildTaskUpdateForTask(previousTaskInfo) != currentTaskUpdate ->
-                    desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate)
+                buildTaskUpdateForTask(previousTaskInfo, postTransitionVisibleFreeformTasks.size())
+                        != currentTaskUpdate ->
+                            desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate)
             }
         }
 
         // find old tasks that were removed
         preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
             if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
-                desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+                desktopModeEventLogger.logTaskRemoved(sessionId,
+                    buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size()))
                 Trace.setCounter(
                     Trace.TRACE_TAG_WINDOW_MANAGER,
                     VISIBLE_TASKS_COUNTER_NAME,
                     postTransitionVisibleFreeformTasks.size().toLong()
                 )
+                SystemProperties.set(VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
+                    postTransitionVisibleFreeformTasks.size().toString())
             }
         }
     }
 
-    private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
+    private fun buildTaskUpdateForTask(taskInfo: TaskInfo, visibleTasks: Int): TaskUpdate {
         val screenBounds = taskInfo.configuration.windowConfiguration.bounds
         val positionInParent = taskInfo.positionInParent
         return TaskUpdate(
@@ -337,6 +343,7 @@
             taskWidth = screenBounds.width(),
             taskX = positionInParent.x,
             taskY = positionInParent.y,
+            visibleTaskCount = visibleTasks,
         )
     }
 
@@ -427,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/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index eca3c1f..dba8c93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.desktopmode
 
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
 import com.android.wm.shell.sysui.ShellCommandHandler
 import java.io.PrintWriter
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
index b24bd10..d6fccd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypes.kt
@@ -17,7 +17,7 @@
 package com.android.wm.shell.desktopmode
 
 import android.view.WindowManager.TransitionType
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TYPES
 
 /**
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 c97066a..1d16980 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
@@ -53,6 +53,7 @@
 import com.android.internal.annotations.VisibleForTesting
 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.Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.internal.protolog.ProtoLog
@@ -68,7 +69,6 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
@@ -88,6 +88,7 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.splitscreen.SplitScreenController
@@ -168,6 +169,9 @@
             }
         }
 
+    @VisibleForTesting
+    var taskbarDesktopTaskListener: TaskbarDesktopTaskListener? = null
+
     /** Task id of the task currently being dragged from fullscreen/split. */
     val draggingTaskId
         get() = dragToDesktopTransitionHandler.draggingTaskId
@@ -322,7 +326,7 @@
             logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId)
             return false
         }
-        logV("moveBackgroundTaskToDesktop with taskId=%d, displayId=%d", taskId)
+        logV("moveBackgroundTaskToDesktop with taskId=%d", taskId)
         // TODO(342378842): Instead of using default display, support multiple displays
         val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask(
             DEFAULT_DISPLAY, wct, taskId)
@@ -609,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.
@@ -650,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)
@@ -658,23 +671,98 @@
         }
     }
 
+    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.
      *
      * @param taskInfo current task that is being snap-resized via dragging or maximize menu button
+     * @param taskSurface the leash of the task being dragged
      * @param currentDragBounds current position of the task leash being dragged (or current task
      *                          bounds if being snapped resize via maximize menu button)
      * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
      */
     fun snapToHalfScreen(
         taskInfo: RunningTaskInfo,
+        taskSurface: SurfaceControl,
         currentDragBounds: Rect,
         position: SnapPosition
     ) {
         val destinationBounds = getSnapBounds(taskInfo, position)
+        if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) {
+            // Handle the case where we attempt to snap resize when already snap resized: the task
+            // position won't need to change but we want to animate the surface going back to the
+            // snapped position from the "dragged-to-the-edge" position.
+            if (destinationBounds != currentDragBounds) {
+                returnToDragStartAnimator.start(
+                    taskInfo.taskId,
+                    taskSurface,
+                    startBounds = currentDragBounds,
+                    endBounds = destinationBounds,
+                    isResizable = taskInfo.isResizeable
+                )
+            }
+            return
+        }
 
-        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)
@@ -693,15 +781,23 @@
     ) {
         releaseVisualIndicator()
         if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) {
+            interactionJankMonitor.begin(
+                taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable"
+            )
+
             // reposition non-resizable app back to its original position before being dragged
             returnToDragStartAnimator.start(
                 taskInfo.taskId,
                 taskSurface,
                 startBounds = currentDragBounds,
-                endBounds = dragStartBounds
+                endBounds = dragStartBounds,
+                isResizable = taskInfo.isResizeable,
             )
         } else {
-            snapToHalfScreen(taskInfo, currentDragBounds, position)
+            interactionJankMonitor.begin(
+                taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable"
+            )
+            snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position)
         }
     }
 
@@ -798,6 +894,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
     }
 
@@ -813,6 +913,7 @@
         val intent = Intent(context, DesktopWallpaperActivity::class.java)
         val options =
             ActivityOptions.makeBasic().apply {
+                launchWindowingMode = WINDOWING_MODE_FULLSCREEN
                 pendingIntentBackgroundActivityStartMode =
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
             }
@@ -1104,12 +1205,11 @@
                 addMoveToDesktopChanges(wct, task)
                 // In some launches home task is moved behind new task being launched. Make sure
                 // that's not the case for launches in desktop.
-                moveHomeTask(wct, toTop = false)
-                // Move existing minimized tasks behind Home
-                taskRepository.getFreeformTasksInZOrder(task.displayId)
-                    .filter { taskId -> taskRepository.isMinimizedTask(taskId) }
-                    .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
-                    .forEach { taskInfo -> wct.reorder(taskInfo.token, /* onTop= */ false) }
+                if (task.baseIntent.flags.and(Intent.FLAG_ACTIVITY_TASK_ON_HOME) != 0) {
+                    bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
+                    wct.reorder(task.token, true)
+                }
+
                 // Desktop Mode is already showing and we're launching a new Task - we might need to
                 // minimize another Task.
                 val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task)
@@ -1149,6 +1249,12 @@
         ) {
             wct.removeTask(task.token)
         }
+        taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+            doesAnyTaskRequireTaskbarRounding(
+                task.displayId,
+                task.id
+            )
+        )
         return if (wct.isEmpty) null else wct
     }
 
@@ -1443,6 +1549,8 @@
         }
         // A freeform drag-move ended, remove the indicator immediately.
         releaseVisualIndicator()
+        taskbarDesktopTaskListener
+            ?.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(taskInfo.displayId))
     }
 
     /**
@@ -1679,17 +1787,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
+                        }
+                    }
                 )
         }
 
@@ -1772,6 +1902,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/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 04506c1..80e106f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -41,7 +41,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 171378f..e87be52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -44,7 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.Cuj;
 import com.android.internal.jank.InteractionJankMonitor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index a7ec203..b036e40e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -18,8 +18,8 @@
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.window.RemoteTransition;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.desktopmode.IDesktopTaskListener;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 
 /**
  * Interface that is exposed to remote callers to manipulate desktop mode features.
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/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
index 24a7d77..f4df42c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -24,6 +24,7 @@
 import android.view.SurfaceControl
 import android.widget.Toast
 import androidx.core.animation.addListener
+import com.android.internal.jank.Cuj
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.wm.shell.R
 import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
@@ -47,7 +48,13 @@
     }
 
     /** Builds new animator and starts animation of task leash reposition. */
-    fun start(taskId: Int, taskSurface: SurfaceControl, startBounds: Rect, endBounds: Rect) {
+    fun start(
+        taskId: Int,
+        taskSurface: SurfaceControl,
+        startBounds: Rect,
+        endBounds: Rect,
+        isResizable: Boolean
+    ) {
         val tx = transactionSupplier.get()
 
         boundsAnimator?.cancel()
@@ -80,12 +87,14 @@
                                 .apply()
                             taskRepositionAnimationListener.onAnimationEnd(taskId)
                             boundsAnimator = null
-                            Toast.makeText(
-                                context,
-                                R.string.desktop_mode_non_resizable_snap_text,
-                                Toast.LENGTH_SHORT
-                            ).show()
-                            // TODO(b/339582583) - add Jank CUJ using interactionJankMonitor
+                            if (!isResizable) {
+                                Toast.makeText(
+                                    context,
+                                    R.string.desktop_mode_non_resizable_snap_text,
+                                    Toast.LENGTH_SHORT
+                                ).show()
+                            }
+                            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
                         }
                     )
                     addUpdateListener { anim ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index bf185a4..96719fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -117,8 +117,8 @@
                             finishCallback.onTransitionFinished(null)
                             initialBounds = null
                             boundsAnimator = null
-                            interactionJankMonitor.end(
-                                Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+                            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW)
+                            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE)
                         }
                     )
                     addUpdateListener { anim ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
new file mode 100644
index 0000000..6013e97
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.education
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.SystemProperties
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Controls app handle education end to end.
+ *
+ * Listen to the user trigger for app handle education, calls an api to check if the education
+ * should be shown and calls an api to show education.
+ */
+@OptIn(kotlinx.coroutines.FlowPreview::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class AppHandleEducationController(
+    private val appHandleEducationFilter: AppHandleEducationFilter,
+    shellTaskOrganizer: ShellTaskOrganizer,
+    private val appHandleEducationDatastoreRepository: AppHandleEducationDatastoreRepository,
+    @ShellMainThread private val applicationCoroutineScope: CoroutineScope
+) {
+  init {
+    runIfEducationFeatureEnabled {
+      // TODO: b/361038716 - Use app handle state flow instead of focus task change flow
+      val focusTaskChangeFlow = focusTaskChangeFlow(shellTaskOrganizer)
+      applicationCoroutineScope.launch {
+        // Central block handling the app's educational flow end-to-end.
+        // This flow listens to the changes to the result of
+        // [WindowingEducationProto#hasEducationViewedTimestampMillis()] in datastore proto object
+        isEducationViewedFlow()
+            .flatMapLatest { isEducationViewed ->
+              if (isEducationViewed) {
+                // If the education is viewed then return emptyFlow() that completes immediately.
+                // This will help us to not listen to focus task changes after the education has
+                // been viewed already.
+                emptyFlow()
+              } else {
+                // This flow listens for focus task changes, which trigger the app handle education.
+                focusTaskChangeFlow
+                    .filter { runningTaskInfo ->
+                      runningTaskInfo.topActivityInfo?.packageName?.let {
+                        appHandleEducationFilter.shouldShowAppHandleEducation(it)
+                      } ?: false && runningTaskInfo.windowingMode != WINDOWING_MODE_FREEFORM
+                    }
+                    .distinctUntilChanged()
+              }
+            }
+            .debounce(
+                APP_HANDLE_EDUCATION_DELAY) // Wait for few seconds, if the focus task changes.
+            // During the delay then current emission will be cancelled.
+            .flowOn(Dispatchers.IO)
+            .collectLatest {
+              // Fire and forget show education suspend function, manage entire lifecycle of
+              // tooltip in UI class.
+            }
+      }
+    }
+  }
+
+  private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
+    if (Flags.enableDesktopWindowingAppHandleEducation()) block()
+  }
+
+  private fun isEducationViewedFlow(): Flow<Boolean> =
+      appHandleEducationDatastoreRepository.dataStoreFlow
+          .map { preferences -> preferences.hasEducationViewedTimestampMillis() }
+          .distinctUntilChanged()
+
+  private fun focusTaskChangeFlow(shellTaskOrganizer: ShellTaskOrganizer): Flow<RunningTaskInfo> =
+      callbackFlow {
+        val focusTaskChange = ShellTaskOrganizer.FocusListener { taskInfo -> trySend(taskInfo) }
+        shellTaskOrganizer.addFocusListener(focusTaskChange)
+        awaitClose { shellTaskOrganizer.removeFocusListener(focusTaskChange) }
+      }
+
+  private companion object {
+    val APP_HANDLE_EDUCATION_DELAY: Long
+      get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
+  }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
index a7fff8a..f420c5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
@@ -25,9 +25,12 @@
 import androidx.datastore.dataStoreFile
 import com.android.framework.protobuf.InvalidProtocolBufferException
 import com.android.internal.annotations.VisibleForTesting
+import java.io.IOException
 import java.io.InputStream
 import java.io.OutputStream
 import java.time.Duration
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.first
 
 /**
@@ -46,17 +49,26 @@
           serializer = WindowingEducationProtoSerializer,
           produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) }))
 
+  /** Provides dataStore.data flow and handles exceptions thrown during collection */
+  val dataStoreFlow: Flow<WindowingEducationProto> =
+      dataStore.data.catch { exception ->
+        // dataStore.data throws an IOException when an error is encountered when reading data
+        if (exception is IOException) {
+          Log.e(
+              TAG,
+              "Error in reading app handle education related data from datastore, data is " +
+                  "stored in a file named $APP_HANDLE_EDUCATION_DATASTORE_FILEPATH",
+              exception)
+        } else {
+          throw exception
+        }
+      }
+
   /**
    * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
    * DataStore is empty or there's an error reading, it returns the default value of Proto.
    */
-  suspend fun windowingEducationProto(): WindowingEducationProto =
-      try {
-        dataStore.data.first()
-      } catch (e: Exception) {
-        Log.e(TAG, "Unable to read from datastore")
-        WindowingEducationProto.getDefaultInstance()
-      }
+  suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
 
   /**
    * Updates [AppHandleEducation.appUsageStats] and
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index 0acc7df..faa97ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -98,9 +98,8 @@
 ### Exposing shared code for use in Launcher
 Launcher doesn't currently build against the Shell library, but needs to have access to some shared
 AIDL interfaces and constants.  Currently, all AIDL files, and classes under the
-`com.android.wm.shell.util` package are automatically built into the `SystemUISharedLib` that
+`com.android.wm.shell.shared` package are automatically built into the `SystemUISharedLib` that
 Launcher uses.
 
-If the new code doesn't fall into those categories, they can be added explicitly in the Shell's
-[Android.bp](/libs/WindowManager/Shell/Android.bp) file under the
-`wm_shell_util-sources` filegroup.
\ No newline at end of file
+If the new code doesn't fall into those categories, they should be moved to the Shell shared
+package (`com.android.wm.shell.shared`) under the `WindowManager-Shell-shared` library.
\ 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/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index b0c896f..4df649c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -43,6 +43,7 @@
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
 import com.android.wm.shell.transition.Transitions;
 
 import java.lang.annotation.Retention;
@@ -418,7 +419,7 @@
         }
 
         SurfaceControl getContentOverlayLeash() {
-            return mContentOverlay == null ? null : mContentOverlay.mLeash;
+            return mContentOverlay == null ? null : mContentOverlay.getLeash();
         }
 
         void setColorContentOverlay(Context context) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 2de545a..ab222c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -91,6 +91,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
@@ -98,6 +99,7 @@
 import java.lang.ref.WeakReference;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.StringJoiner;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
@@ -361,8 +363,7 @@
     SurfaceControl mPipOverlay;
 
     /**
-     * The app bounds used for the buffer size of the
-     * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+     * The app bounds used for the buffer size of the {@link PipContentOverlay.PipAppIconOverlay}.
      *
      * Note that this is empty if the overlay is removed or if it's some other type of overlay
      * defined in {@link PipContentOverlay}.
@@ -831,6 +832,7 @@
                     mPictureInPictureParams.getTitle());
             mPipParamsChangedForwarder.notifySubtitleChanged(
                     mPictureInPictureParams.getSubtitle());
+            logRemoteActions(mPictureInPictureParams);
         }
 
         mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
@@ -1112,6 +1114,7 @@
         }
         applyNewPictureInPictureParams(newParams);
         mPictureInPictureParams = newParams;
+        logRemoteActions(mPictureInPictureParams);
     }
 
     @Override
@@ -1420,6 +1423,16 @@
         }
     }
 
+    private void logRemoteActions(@NonNull PictureInPictureParams params) {
+        StringJoiner sj = new StringJoiner("|", "[", "]");
+        if (params.hasSetActions()) {
+            params.getActions().forEach((action) -> sj.add(action.getTitle()));
+        }
+
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: PIP remote actions=%s", TAG, sj.toString());
+    }
+
     /**
      * Animates resizing of the pinned stack given the duration.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 7ba6ec4..05d1984 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -41,6 +41,7 @@
 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
 import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_CLEANUP_PIP_EXIT;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
@@ -74,6 +75,7 @@
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.CounterRotatorHelper;
@@ -122,6 +124,8 @@
     @Nullable
     private IBinder mMoveToBackTransition;
     private IBinder mRequestedEnterTransition;
+    private IBinder mCleanupTransition;
+
     private WindowContainerToken mRequestedEnterTask;
     /** The Task window that is currently in PIP windowing mode. */
     @Nullable
@@ -232,10 +236,12 @@
 
         // Exiting PIP.
         final int type = info.getType();
-        if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)) {
+        if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)
+                || transition.equals(mCleanupTransition)) {
             mExitDestinationBounds.setEmpty();
             mExitTransition = null;
             mMoveToBackTransition = null;
+            mCleanupTransition = null;
             mHasFadeOut = false;
             if (mFinishCallback != null) {
                 callFinishCallback(null /* wct */);
@@ -269,6 +275,9 @@
                     removePipImmediately(info, startTransaction, finishTransaction, finishCallback,
                             pipTaskInfo);
                     break;
+                case TRANSIT_CLEANUP_PIP_EXIT:
+                    cleanupPipExitTransition(startTransaction, finishCallback);
+                    break;
                 default:
                     throw new IllegalStateException("mExitTransition with unexpected transit type="
                             + transitTypeToString(type));
@@ -768,7 +777,19 @@
                 mPipAnimationController.resetAnimatorState();
                 finishTransaction.remove(pipLeash);
             }
-            finishCallback.onTransitionFinished(wct);
+
+            if (mFixedRotationState == FIXED_ROTATION_TRANSITION) {
+                // TODO(b/358226697): start a new transition with the WCT instead of applying it in
+                //  the {@link finishCallback}, to ensure shell creates a transition for it.
+                finishCallback.onTransitionFinished(wct);
+            } else {
+                // Apply wct in separate transition so that it can be correctly handled by the
+                // {@link FreeformTaskTransitionObserver} when desktop windowing (which does not
+                // utilize fixed rotation transitions for exiting pip) is enabled (See b/288910069).
+                mCleanupTransition = mTransitions.startTransition(
+                        TRANSIT_CLEANUP_PIP_EXIT, wct, this);
+                finishCallback.onTransitionFinished(null);
+            }
         };
         mFinishTransaction = finishTransaction;
 
@@ -914,6 +935,16 @@
         finishCallback.onTransitionFinished(null);
     }
 
+    /**
+     * For {@link Transitions#TRANSIT_CLEANUP_PIP_EXIT} which applies final config changes needed
+     * after the exit from pip transition animation finishes.
+     */
+    private void cleanupPipExitTransition(@NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        startTransaction.apply();
+        finishCallback.onTransitionFinished(null);
+    }
+
     /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
     private boolean isEnteringPip(@NonNull TransitionInfo info) {
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 0d2b8e7..06d2311 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -35,9 +35,9 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.bubbles.DismissCircleView;
-import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.shared.bubbles.DismissCircleView;
+import com.android.wm.shell.shared.bubbles.DismissView;
 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import kotlin.Unit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
index 8a9302b..8ebdc96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java
@@ -22,6 +22,7 @@
 import android.annotation.IntDef;
 import android.content.Context;
 import android.graphics.Rect;
+import android.view.Surface;
 import android.view.SurfaceControl;
 
 import androidx.annotation.NonNull;
@@ -51,8 +52,10 @@
 
     @NonNull private final SurfaceControl mLeash;
     private final SurfaceControl.Transaction mStartTransaction;
-    private final int mEnterAnimationDuration;
+    private final SurfaceControl.Transaction mFinishTransaction;
+    private final int mEnterExitAnimationDuration;
     private final @BOUNDS int mDirection;
+    private final @Surface.Rotation int mRotation;
 
     // optional callbacks for tracking animation start and end
     @Nullable private Runnable mAnimationStartCallback;
@@ -62,37 +65,59 @@
     private final Rect mStartBounds = new Rect();
     private final Rect mEndBounds = new Rect();
 
+    @Nullable private final Rect mSourceRectHint;
+    private final Rect mSourceRectHintInsets = new Rect();
+    private final Rect mZeroInsets = new Rect(0, 0, 0, 0);
+
     // Bounds updated by the evaluator as animator is running.
     private final Rect mAnimatedRect = new Rect();
 
     private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
     private final RectEvaluator mRectEvaluator;
+    private final RectEvaluator mInsetEvaluator;
     private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
 
     public PipEnterExitAnimator(Context context,
             @NonNull SurfaceControl leash,
             SurfaceControl.Transaction startTransaction,
+            SurfaceControl.Transaction finishTransaction,
             @NonNull Rect baseBounds,
             @NonNull Rect startBounds,
             @NonNull Rect endBounds,
-            @BOUNDS int direction) {
+            @Nullable Rect sourceRectHint,
+            @BOUNDS int direction,
+            @Surface.Rotation int rotation) {
         mLeash = leash;
         mStartTransaction = startTransaction;
+        mFinishTransaction = finishTransaction;
         mBaseBounds.set(baseBounds);
         mStartBounds.set(startBounds);
         mAnimatedRect.set(startBounds);
         mEndBounds.set(endBounds);
         mRectEvaluator = new RectEvaluator(mAnimatedRect);
+        mInsetEvaluator = new RectEvaluator(new Rect());
         mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context);
         mDirection = direction;
+        mRotation = rotation;
+
+        mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null;
+        if (mSourceRectHint != null) {
+            mSourceRectHintInsets.set(
+                    mSourceRectHint.left - mBaseBounds.left,
+                    mSourceRectHint.top - mBaseBounds.top,
+                    mBaseBounds.right - mSourceRectHint.right,
+                    mBaseBounds.bottom - mSourceRectHint.bottom
+            );
+        }
 
         mSurfaceControlTransactionFactory =
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
-        mEnterAnimationDuration = context.getResources()
+        mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipEnterAnimationDuration);
 
-        setDuration(mEnterAnimationDuration);
+        setObjectValues(startBounds, endBounds);
+        setDuration(mEnterExitAnimationDuration);
         setEvaluator(mRectEvaluator);
         addListener(this);
         addUpdateListener(this);
@@ -118,6 +143,14 @@
 
     @Override
     public void onAnimationEnd(@NonNull Animator animation) {
+        if (mFinishTransaction != null) {
+            // finishTransaction might override some state (eg. corner radii) so we want to
+            // manually set the state to the end of the animation
+            mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash, mSourceRectHint,
+                            mBaseBounds, mAnimatedRect, getInsets(1f), isInPipDirection(), 1f)
+                    .round(mFinishTransaction, mLeash, isInPipDirection())
+                    .shadow(mFinishTransaction, mLeash, isInPipDirection());
+        }
         if (mAnimationEndCallback != null) {
             mAnimationEndCallback.run();
         }
@@ -127,19 +160,32 @@
     public void onAnimationUpdate(@NonNull ValueAnimator animation) {
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         final float fraction = getAnimatedFraction();
+        Rect insets = getInsets(fraction);
+
         // TODO (b/350801661): implement fixed rotation
 
-        mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, null,
-                mBaseBounds, mAnimatedRect, null, isInPipDirection(), fraction)
+        mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint,
+                mBaseBounds, mAnimatedRect, insets, isInPipDirection(), fraction)
                 .round(tx, mLeash, isInPipDirection())
                 .shadow(tx, mLeash, isInPipDirection());
         tx.apply();
     }
 
+    private Rect getInsets(float fraction) {
+        Rect startInsets = isInPipDirection() ? mZeroInsets : mSourceRectHintInsets;
+        Rect endInsets = isInPipDirection() ? mSourceRectHintInsets : mZeroInsets;
+
+        return mInsetEvaluator.evaluate(fraction, startInsets, endInsets);
+    }
+
     private boolean isInPipDirection() {
         return mDirection == BOUNDS_ENTER;
     }
 
+    private boolean isOutPipDirection() {
+        return mDirection == BOUNDS_EXIT;
+    }
+
     // no-ops
 
     @Override
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/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
index e04178e..b3070f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
@@ -35,9 +35,9 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.bubbles.DismissCircleView;
-import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.shared.bubbles.DismissCircleView;
+import com.android.wm.shell.shared.bubbles.DismissView;
 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import kotlin.Unit;
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..262c14d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -0,0 +1,167 @@
+/*
+ * 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.NonNull;
+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());
+    }
+
+    @NonNull
+    public PictureInPictureParams getPictureInPictureParams() {
+        return mPictureInPictureParams;
+    }
+
+    @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 ed18712..f93233e 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
@@ -36,6 +36,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
@@ -50,10 +51,10 @@
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipMenuController;
 import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.pip.PipContentOverlay;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
 import com.android.wm.shell.pip2.animation.PipEnterExitAnimator;
+import com.android.wm.shell.shared.pip.PipContentOverlay;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
@@ -87,6 +88,7 @@
     //
 
     private final Context mContext;
+    private final PipTaskListener mPipTaskListener;
     private final PipScheduler mPipScheduler;
     private final PipTransitionState mPipTransitionState;
 
@@ -118,6 +120,7 @@
             PipBoundsState pipBoundsState,
             PipMenuController pipMenuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipTaskListener pipTaskListener,
             PipScheduler pipScheduler,
             PipTransitionState pipTransitionState,
             PipUiStateChangeController pipUiStateChangeController) {
@@ -125,6 +128,7 @@
                 pipBoundsAlgorithm);
 
         mContext = context;
+        mPipTaskListener = pipTaskListener;
         mPipScheduler = pipScheduler;
         mPipScheduler.setPipTransitionController(this);
         mPipTransitionState = pipTransitionState;
@@ -395,17 +399,22 @@
         SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
         Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition.");
 
+        Rect sourceRectHint = null;
+        if (pipChange.getTaskInfo() != null
+                && pipChange.getTaskInfo().pictureInPictureParams != null) {
+            sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
+        }
+
         PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
-                startTransaction, startBounds, startBounds, endBounds,
-                PipEnterExitAnimator.BOUNDS_ENTER);
+                startTransaction, finishTransaction, startBounds, startBounds, endBounds,
+                sourceRectHint, PipEnterExitAnimator.BOUNDS_ENTER, Surface.ROTATION_0);
 
         tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(),
                 this::onClientDrawAtTransitionEnd);
         finishWct.setBoundsChangeTransaction(pipTaskToken, tx);
 
-        animator.setAnimationEndCallback(() -> {
-            finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct);
-        });
+        animator.setAnimationEndCallback(() ->
+                finishCallback.onTransitionFinished(finishWct));
 
         animator.start();
         return true;
@@ -449,19 +458,53 @@
 
         TransitionInfo.Change pipChange = getChangeByToken(info, pipToken);
         if (pipChange == null) {
-            return false;
+            // pipChange is null, check to see if we've reparented the PIP activity for
+            // the multi activity case. If so we should use the activity leash instead
+            for (TransitionInfo.Change change : info.getChanges()) {
+                if (change.getTaskInfo() == null
+                        && change.getLastParent() != null
+                        && change.getLastParent().equals(pipToken)) {
+                    pipChange = change;
+                    break;
+                }
+            }
+
+            // failsafe
+            if (pipChange == null) {
+                return false;
+            }
+        }
+
+        // for multi activity, we need to manually set the leash layer
+        if (pipChange.getTaskInfo() == null) {
+            TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent());
+            if (parent != null) {
+                startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1);
+            }
         }
 
         Rect startBounds = pipChange.getStartAbsBounds();
         Rect endBounds = pipChange.getEndAbsBounds();
         SurfaceControl pipLeash = pipChange.getLeash();
+        Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition.");
+
+        Rect sourceRectHint = null;
+        if (pipChange.getTaskInfo() != null
+                && pipChange.getTaskInfo().pictureInPictureParams != null) {
+            // single activity
+            sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
+        } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) {
+            // multi activity
+            sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint();
+        }
 
         PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash,
-                startTransaction, startBounds, startBounds, endBounds,
-                PipEnterExitAnimator.BOUNDS_EXIT);
+                startTransaction, finishTransaction, endBounds, startBounds, endBounds,
+                sourceRectHint, PipEnterExitAnimator.BOUNDS_EXIT, Surface.ROTATION_0);
+
         animator.setAnimationEndCallback(() -> {
-            finishCallback.onTransitionFinished(null);
             mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
+            finishCallback.onTransitionFinished(null);
         });
 
         animator.start();
@@ -510,6 +553,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/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 4048c5b..ebfd357 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -24,7 +24,7 @@
 import android.view.IRecentsAnimationRunner;
 
 import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 
 /**
  * Interface that is exposed to remote callers to fetch recent tasks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 77b8663..8c5d1e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -19,8 +19,8 @@
 import android.annotation.Nullable;
 import android.graphics.Color;
 
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
 
 import java.util.List;
 import java.util.concurrent.Executor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 2f0af855..9af33a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -21,9 +21,12 @@
 
 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
 
+import android.Manifest;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IApplicationThread;
+import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -51,16 +54,16 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -158,6 +161,7 @@
         return new IRecentTasksImpl(this);
     }
 
+    @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
     private void onInit() {
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_RECENT_TASKS,
                 this::createExternalInterface, this);
@@ -168,6 +172,8 @@
             mTaskStackTransitionObserver.addTaskStackTransitionObserverListener(this,
                     mMainExecutor);
         }
+        mContext.getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
+                mMainExecutor, isKeyguardLocked -> notifyRecentTasksChanged());
     }
 
     void setTransitionHandler(RecentsTransitionHandler handler) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 7a9eb1c..c90da05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -30,7 +30,7 @@
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
-import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
 
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index 1cbb8bb..1dad413 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -33,7 +33,7 @@
 
 /**
  * Main stage for split-screen mode. When split-screen is active all standard activity types launch
- * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
+ * on the main stage, except for task that are explicitly pinned to the {@link StageTaskListener}.
  * @see StageCoordinator
  */
 class MainStage extends StageTaskListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
deleted file mode 100644
index 27fd309..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.splitscreen;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.view.SurfaceSession;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.protolog.ProtoLog;
-import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.windowdecor.WindowDecorViewModel;
-
-import java.util.Optional;
-
-/**
- * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
- * here. All other task are launch in the {@link MainStage}.
- *
- * @see StageCoordinator
- */
-class SideStage extends StageTaskListener {
-    private static final String TAG = SideStage.class.getSimpleName();
-
-    SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
-            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
-            SurfaceSession surfaceSession, IconProvider iconProvider,
-            Optional<WindowDecorViewModel> windowDecorViewModel) {
-        super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
-                iconProvider, windowDecorViewModel);
-    }
-
-    boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
-        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b",
-                mChildrenTaskInfo.size(), toTop);
-        if (mChildrenTaskInfo.size() == 0) return false;
-        wct.reparentTasks(
-                mRootTaskInfo.token,
-                null /* newParent */,
-                null /* windowingModes */,
-                null /* activityTypes */,
-                toTop);
-        return true;
-    }
-
-    boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
-        final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
-        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove side stage task: task=%d exists=%b", taskId,
-                task != null);
-        if (task == null) return false;
-        wct.reparent(task.token, newParent, false /* onTop */);
-        return true;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index a6233dc9..526c1d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -50,7 +50,7 @@
 
     /**
      * The side stage type.
-     * @see SideStage
+     * @see StageTaskListener
      */
     int STAGE_TYPE_SIDE = 1;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 27ded57..cea995d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -321,8 +321,10 @@
                 0 /* enterReason */,
                 0 /* exitReason */,
                 mLastSplitRatio,
-                0 /* mainStagePosition */, 0 /* mainStageUid */,
-                0 /* sideStagePosition */, 0 /* sideStageUid */,
+                mLastMainStagePosition,
+                mLastMainStageUid,
+                mLastSideStagePosition,
+                mLastSideStageUid,
                 0 /* dragInstanceId */,
                 mLoggerSessionId.getId());
     }
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 0b5c751..a10c1db 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;
@@ -133,13 +134,13 @@
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.split.SplitBounds;
 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
 import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.SplitBounds;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -154,13 +155,13 @@
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
- * {@link SideStage} stages.
+ * other stages.
  * Some high-level rules:
- * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
+ * - The {@link StageCoordinator} is only considered active if the other stages contain at
  * least one child task.
  * - The {@link MainStage} should only have children if the coordinator is active.
  * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
- * and {@link SideStage} are visible.
+ * and other stages are visible.
  * - Both stages are put under a single-top root task.
  * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
  * {@link #onStageHasChildrenChanged(StageListenerImpl).}
@@ -175,7 +176,7 @@
 
     private final MainStage mMainStage;
     private final StageListenerImpl mMainStageListener = new StageListenerImpl();
-    private final SideStage mSideStage;
+    private final StageTaskListener mSideStage;
     private final StageListenerImpl mSideStageListener = new StageListenerImpl();
     @SplitPosition
     private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -337,7 +338,7 @@
                 mSurfaceSession,
                 iconProvider,
                 mWindowDecorViewModel);
-        mSideStage = new SideStage(
+        mSideStage = new StageTaskListener(
                 mContext,
                 mTaskOrganizer,
                 mDisplayId,
@@ -366,7 +367,7 @@
 
     @VisibleForTesting
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
-            ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage,
+            ShellTaskOrganizer taskOrganizer, MainStage mainStage, StageTaskListener sideStage,
             DisplayController displayController, DisplayImeController displayImeController,
             DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
             Transitions transitions, TransactionPool transactionPool, ShellExecutor mainExecutor,
@@ -423,6 +424,15 @@
         return mMainStage.isActive();
     }
 
+    /**
+     * Deactivates main stage by removing the stage from the top level split root (usually when a
+     * task underneath gets removed from the stage root).
+     * @param reparentToTop whether we want to put the stage root back on top
+     */
+    private void deactivateSplit(WindowContainerTransaction wct, boolean reparentToTop) {
+        mMainStage.deactivate(wct, reparentToTop);
+    }
+
     /** @return whether this transition-request has the launch-adjacent flag. */
     public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) {
         final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
@@ -497,10 +507,10 @@
 
         /**
          * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
-         * {@link SideStage} no longer has children.
+         * other stages no longer have children.
          */
         final boolean result = mSideStage.removeTask(taskId,
-                mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
+                isSplitActive() ? mMainStage.mRootTaskInfo.token : null,
                 wct);
         mTaskOrganizer.applyTransaction(wct);
         return result;
@@ -617,7 +627,7 @@
         }
 
         // If split screen is not activated, we're expecting to open a pair of apps to split.
-        final int extraTransitType = mMainStage.isActive()
+        final int extraTransitType = isSplitActive()
                 ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
         prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
 
@@ -660,7 +670,7 @@
         }
 
         // If split screen is not activated, we're expecting to open a pair of apps to split.
-        final int extraTransitType = mMainStage.isActive()
+        final int extraTransitType = isSplitActive()
                 ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
         prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
 
@@ -684,6 +694,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 +725,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 +769,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
@@ -771,7 +802,7 @@
     private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
             @Nullable Bundle mainOptions, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
-        if (!mMainStage.isActive()) {
+        if (!isSplitActive()) {
             // Build a request WCT that will launch both apps such that task 0 is on the main stage
             // while task 1 is on the side stage.
             mMainStage.activate(wct, false /* reparent */);
@@ -838,7 +869,7 @@
             return;
         }
 
-        if (!mMainStage.isActive()) {
+        if (!isSplitActive()) {
             // Build a request WCT that will launch both apps such that task 0 is on the main stage
             // while task 1 is on the side stage.
             mMainStage.activate(wct, false /* reparent */);
@@ -1088,7 +1119,7 @@
      */
     void onKeyguardStateChanged(boolean active, boolean occludingTaskRunning) {
         mKeyguardActive = active;
-        if (!mMainStage.isActive()) {
+        if (!isSplitActive()) {
             return;
         }
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
@@ -1132,7 +1163,7 @@
      * will do a no-op.
      */
     void dismissSplitKeepingLastActiveStage(@ExitReason int reason) {
-        if (!mMainStage.isActive() || mLastActiveStage == STAGE_TYPE_UNDEFINED) {
+        if (!isSplitActive() || mLastActiveStage == STAGE_TYPE_UNDEFINED) {
             // no-op
             return;
         }
@@ -1155,8 +1186,8 @@
     private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
             @ExitReason int exitReason) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
-                childrenToTop == mMainStage, exitReasonToString(exitReason), mMainStage.isActive());
-        if (!mMainStage.isActive()) return;
+                childrenToTop == mMainStage, exitReasonToString(exitReason), isSplitActive());
+        if (!isSplitActive()) return;
 
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         applyExitSplitScreen(childrenToTop, wct, exitReason);
@@ -1166,7 +1197,7 @@
             WindowContainerTransaction wct, @ExitReason int exitReason) {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "applyExitSplitScreen: reason=%s",
                 exitReasonToString(exitReason));
-        if (!mMainStage.isActive() || mIsExiting) return;
+        if (!isSplitActive() || mIsExiting) return;
 
         onSplitScreenExit();
         clearSplitPairedInRecents(exitReason);
@@ -1178,7 +1209,7 @@
         mSplitLayout.getInvisibleBounds(mTempRect1);
         if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
             mSideStage.removeAllTasks(wct, false /* toTop */);
-            mMainStage.deactivate(wct, false /* toTop */);
+            deactivateSplit(wct, false /* reparentToTop */);
             wct.reorder(mRootTaskInfo.token, false /* onTop */);
             setRootForceTranslucent(true, wct);
             wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
@@ -1207,7 +1238,7 @@
                 childrenToTop.fadeOutDecor(() -> {
                     WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
                     mIsExiting = false;
-                    mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
+                    deactivateSplit(finishedWCT, childrenToTop == mMainStage /* reparentToTop */);
                     mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
                     finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
                     setRootForceTranslucent(true, finishedWCT);
@@ -1230,7 +1261,7 @@
     }
 
     void dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
-        if (!mMainStage.isActive()) return;
+        if (!isSplitActive()) return;
         final int stage = getStageOfTask(toTopTaskId);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         prepareExitSplitScreen(stage, wct);
@@ -1340,10 +1371,10 @@
      */
     void prepareExitSplitScreen(@StageType int stageToTop,
             @NonNull WindowContainerTransaction wct) {
-        if (!mMainStage.isActive()) return;
+        if (!isSplitActive()) return;
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%d", stageToTop);
         mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
-        mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
+        deactivateSplit(wct, stageToTop == STAGE_TYPE_MAIN);
     }
 
     private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
@@ -1640,7 +1671,7 @@
         mRootTaskInfo = taskInfo;
         if (mSplitLayout != null
                 && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
-                && mMainStage.isActive()) {
+                && isSplitActive()) {
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: task=%d updating",
                     taskInfo.taskId);
             // Clear the divider remote animating flag as the divider will be re-rendered to apply
@@ -1894,7 +1925,7 @@
                 stageListener == mMainStageListener);
         final boolean hasChildren = stageListener.mHasChildren;
         final boolean isSideStage = stageListener == mSideStageListener;
-        if (!hasChildren && !mIsExiting && mMainStage.isActive()) {
+        if (!hasChildren && !mIsExiting && isSplitActive()) {
             if (isSideStage && mMainStageListener.mVisible) {
                 // Exit to main stage if side stage no longer has children.
                 mSplitLayout.flingDividerToDismiss(
@@ -1909,7 +1940,7 @@
                 // Dismiss split screen in the background once any sides of the split become empty.
                 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
             }
-        } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
+        } else if (isSideStage && hasChildren && !isSplitActive()) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             prepareEnterSplitScreen(wct);
 
@@ -2124,7 +2155,7 @@
 
     private void onDisplayChange(int displayId, int fromRotation, int toRotation,
             @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
-        if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) {
+        if (displayId != DEFAULT_DISPLAY || !isSplitActive()) {
             return;
         }
 
@@ -2419,7 +2450,7 @@
             // Not entering or exiting, so just do some house-keeping and validation.
 
             // If we're not in split-mode, just abort so something else can handle it.
-            if (!mMainStage.isActive()) return false;
+            if (!isSplitActive()) return false;
 
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startAnimation: transition=%d", info.getDebugId());
             mSplitLayout.setFreezeDividerWindow(false);
@@ -2661,7 +2692,7 @@
     public void onTransitionAnimationComplete() {
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionAnimationComplete");
         // If still playing, let it finish.
-        if (!mMainStage.isActive() && !mIsExiting) {
+        if (!isSplitActive() && !mIsExiting) {
             // Update divider state after animation so that it is still around and positioned
             // properly for the animation itself.
             mSplitLayout.release();
@@ -3125,7 +3156,7 @@
                 + (mSplitLayout != null ? mSplitLayout.isLeftRightSplit() : "null"));
         pw.println(innerPrefix + "MainStage");
         pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
-        pw.println(childPrefix + "isActive=" + mMainStage.isActive());
+        pw.println(childPrefix + "isActive=" + isSplitActive());
         mMainStage.dump(pw, childPrefix);
         pw.println(innerPrefix + "MainStageListener");
         mMainStageListener.dump(pw, childPrefix);
@@ -3245,7 +3276,7 @@
         @Override
         public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
             ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo);
-            if (mMainStage.isActive()) {
+            if (isSplitActive()) {
                 final boolean isMainStage = mMainStageListener == this;
 
                 // If visible, we preserve the app and keep it running. If an app becomes
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 99f3832..4593553 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -475,6 +475,30 @@
         });
     }
 
+    // --------
+    // Previously only used in SideStage
+    boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b",
+                mChildrenTaskInfo.size(), toTop);
+        if (mChildrenTaskInfo.size() == 0) return false;
+        wct.reparentTasks(
+                mRootTaskInfo.token,
+                null /* newParent */,
+                null /* windowingModes */,
+                null /* activityTypes */,
+                toTop);
+        return true;
+    }
+
+    boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
+        final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove side stage task: task=%d exists=%b", taskId,
+                task != null);
+        if (task == null) return false;
+        wct.reparent(task.token, newParent, false /* onTop */);
+        return true;
+    }
+
     private void sendStatusChanged() {
         mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index b18feefe..81f444b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -352,12 +352,17 @@
     /** Extract the window background color from {@code attrs}. */
     private static int peekWindowBGColor(Context context, SplashScreenWindowAttrs attrs) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "peekWindowBGColor");
-        final Drawable themeBGDrawable;
+        Drawable themeBGDrawable = null;
         if (attrs.mWindowBgColor != 0) {
             themeBGDrawable = new ColorDrawable(attrs.mWindowBgColor);
         } else if (attrs.mWindowBgResId != 0) {
-            themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
-        } else {
+            try {
+                themeBGDrawable = context.getDrawable(attrs.mWindowBgResId);
+            } catch (Resources.NotFoundException e) {
+                Slog.w(TAG, "Unable get drawable from resource", e);
+            }
+        }
+        if (themeBGDrawable == null) {
             themeBGDrawable = createDefaultBackgroundDrawable();
             Slog.w(TAG, "Window background does not exist, using " + themeBGDrawable);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
index a85188a..82c0aaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java
@@ -118,6 +118,13 @@
         mTaskViewTaskController.startShortcutActivity(shortcut, options, launchBounds);
     }
 
+    /**
+     * Moves the current task in taskview out of the view and back to fullscreen.
+     */
+    public void moveToFullscreen() {
+        mTaskViewTaskController.moveToFullscreen();
+    }
+
     @Override
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
         if (mTaskViewTaskController.isUsingShellTransitions()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 9750d3e..e74342e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.taskview;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 
 import android.annotation.NonNull;
@@ -256,6 +257,24 @@
         mTaskViewTransitions.startInstantTransition(TRANSIT_CHANGE, wct);
     }
 
+    /**
+     * Moves the current task in TaskView out of the view and back to fullscreen.
+     */
+    public void moveToFullscreen() {
+        if (mTaskToken == null) return;
+        mShellExecutor.execute(() -> {
+            WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.setWindowingMode(mTaskToken, WINDOWING_MODE_UNDEFINED);
+            wct.setAlwaysOnTop(mTaskToken, false);
+            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+            mTaskViewTransitions.moveTaskViewToFullscreen(wct, this);
+            if (mListener != null) {
+                // Task is being "removed" from the clients perspective
+                mListener.onTaskRemovalStarted(mTaskInfo.taskId);
+            }
+        });
+    }
+
     private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) {
         final Binder launchCookie = new Binder();
         mShellExecutor.execute(() -> {
@@ -585,7 +604,6 @@
                 });
             }
             mTaskViewBase.onTaskVanished(taskInfo);
-            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, false);
         }
     }
 
@@ -699,6 +717,9 @@
             mTaskViewBase.setResizeBgColor(startTransaction, backgroundColor);
         }
 
+        // After the embedded task has appeared, set it to non-trimmable. This is important
+        // to prevent recents from trimming and removing the embedded task.
+        wct.setTaskTrimmableFromRecents(taskInfo.token, false /* isTrimmableFromRecents */);
         mTaskViewBase.onTaskAppeared(mTaskInfo, mTaskLeash);
         if (mListener != null) {
             final int taskId = mTaskInfo.taskId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 15fe7ab..39648f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -236,6 +236,12 @@
         startNextTransition();
     }
 
+    void moveTaskViewToFullscreen(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskViewTaskController taskView) {
+        mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
+        startNextTransition();
+    }
+
     /** Starts a new transition to make the given {@code taskView} visible. */
     public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
         setTaskViewVisible(taskView, visible, false /* reorder */);
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 a9a4e10..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
@@ -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/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 9b27e41..3d79a1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -30,6 +30,7 @@
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 
+import com.android.window.flags.Flags;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -71,9 +72,17 @@
 
             final int mode = change.getMode();
             final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
-            if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
-                    && (TransitionUtil.isOpenOrCloseMode(mode) || isBackGesture)) {
-                notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode) || isBackGesture);
+            if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+                if (Flags.migratePredictiveBackTransition()) {
+                    if (!isBackGesture && TransitionUtil.isOpenOrCloseMode(mode)) {
+                        notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode));
+                    }
+                } else {
+                    if (TransitionUtil.isOpenOrCloseMode(mode) || isBackGesture) {
+                        notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode)
+                                || isBackGesture);
+                    }
+                }
             }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index f0d3668..7dc336b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -193,6 +193,9 @@
     /** Remote Transition that split accepts but ultimately needs to be animated by the remote. */
     public static final int TRANSIT_SPLIT_PASSTHROUGH = TRANSIT_FIRST_CUSTOM + 18;
 
+    /** Transition to set windowing mode after exit pip transition is finished animating. */
+    public static final int TRANSIT_CLEANUP_PIP_EXIT = WindowManager.TRANSIT_FIRST_CUSTOM + 19;
+
     /** Transition type for desktop mode transitions. */
     public static final int TRANSIT_DESKTOP_MODE_TYPES =
             WindowManager.TRANSIT_FIRST_CUSTOM + 100;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl
deleted file mode 100644
index 15797cd..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.util;
-
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
deleted file mode 100644
index 482aaab..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file KtProtolog.kt = file:platform/development:/tools/winscope/OWNERS
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 501e856..11976ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -27,7 +27,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -181,7 +180,6 @@
         }
 
         decoration.relayout(taskInfo);
-        setupCaptionColor(taskInfo, decoration);
     }
 
     @Override
@@ -243,15 +241,6 @@
         decoration.close();
     }
 
-    private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
-        if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
-            decoration.setCaptionColor(Color.TRANSPARENT);
-        } else {
-            final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
-            decoration.setCaptionColor(statusBarColor);
-        }
-    }
-
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             return true;
@@ -320,7 +309,6 @@
         windowDecoration.setTaskDragResizer(taskPositioner);
         windowDecoration.relayout(taskInfo, startT, finishT,
                 false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */);
-        setupCaptionColor(taskInfo, windowDecoration);
     }
 
     private class CaptionTouchEventListener implements
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 231570f..349ee0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -35,7 +35,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
 import android.util.Size;
 import android.view.Choreographer;
@@ -310,6 +309,9 @@
     }
 
     private void bindData(View rootView, RunningTaskInfo taskInfo) {
+        // Set up the tint first so that the drawable can be stylized when loaded.
+        setupCaptionColor(taskInfo);
+
         final boolean isFullscreen =
                 taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
         rootView.findViewById(R.id.maximize_window)
@@ -317,7 +319,16 @@
                         : R.drawable.decor_maximize_button_dark);
     }
 
-    void setCaptionColor(int captionColor) {
+    private void setupCaptionColor(RunningTaskInfo taskInfo) {
+        if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
+            setCaptionColor(Color.TRANSPARENT);
+        } else {
+            final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+            setCaptionColor(statusBarColor);
+        }
+    }
+
+    private void setCaptionColor(int captionColor) {
         if (mResult.mRootView == null) {
             return;
         }
@@ -334,20 +345,16 @@
                 caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
 
         final View back = caption.findViewById(R.id.back_button);
-        final VectorDrawable backBackground = (VectorDrawable) back.getBackground();
-        backBackground.setTintList(buttonTintColor);
+        back.setBackgroundTintList(buttonTintColor);
 
         final View minimize = caption.findViewById(R.id.minimize_window);
-        final VectorDrawable minimizeBackground = (VectorDrawable) minimize.getBackground();
-        minimizeBackground.setTintList(buttonTintColor);
+        minimize.setBackgroundTintList(buttonTintColor);
 
         final View maximize = caption.findViewById(R.id.maximize_window);
-        final VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
-        maximizeBackground.setTintList(buttonTintColor);
+        maximize.setBackgroundTintList(buttonTintColor);
 
         final View close = caption.findViewById(R.id.close_window);
-        final VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
-        closeBackground.setTintList(buttonTintColor);
+        close.setBackgroundTintList(buttonTintColor);
     }
 
     boolean isHandlingDragResize() {
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 b1cb834..c88c1e2 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,7 +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.common.desktopmode.DesktopModeTransitionSource;
+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;
@@ -107,6 +107,7 @@
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
@@ -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
@@ -480,7 +489,11 @@
             Toast.makeText(mContext,
                     R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
         } else {
-            mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
+            mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext,
+                    Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
+            mDesktopTasksController.snapToHalfScreen(
+                    decoration.mTaskInfo,
+                    decoration.mTaskSurface,
                     decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
                     left ? SnapPosition.LEFT : SnapPosition.RIGHT);
         }
@@ -494,16 +507,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 8e87d0f..aa43c8d 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
@@ -26,7 +26,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
-import static com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
@@ -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;
@@ -79,10 +80,10 @@
 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.common.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
@@ -113,6 +114,7 @@
     private final Choreographer mChoreographer;
     private final SyncTransactionQueue mSyncQueue;
     private final SplitScreenController mSplitScreenController;
+    private final WindowManagerWrapper mWindowManagerWrapper;
 
     private WindowDecorationViewHolder mWindowDecorViewHolder;
     private View.OnClickListener mOnCaptionButtonClickListener;
@@ -187,7 +189,9 @@
                 taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue,
                 rootTaskDisplayAreaOrganizer, genericLinksParser, SurfaceControl.Builder::new,
                 SurfaceControl.Transaction::new,  WindowContainerTransaction::new,
-                SurfaceControl::new, new SurfaceControlViewHostFactory() {},
+                SurfaceControl::new, new WindowManagerWrapper(
+                        context.getSystemService(WindowManager.class)),
+                new SurfaceControlViewHostFactory() {},
                 DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE,
                 multiInstanceHelper);
     }
@@ -210,6 +214,7 @@
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
             Supplier<SurfaceControl> surfaceControlSupplier,
+            WindowManagerWrapper windowManagerWrapper,
             SurfaceControlViewHostFactory surfaceControlViewHostFactory,
             MaximizeMenuFactory maximizeMenuFactory,
             HandleMenuFactory handleMenuFactory,
@@ -228,6 +233,7 @@
         mMaximizeMenuFactory = maximizeMenuFactory;
         mHandleMenuFactory = handleMenuFactory;
         mMultiInstanceHelper = multiInstanceHelper;
+        mWindowManagerWrapper = windowManagerWrapper;
     }
 
     /**
@@ -480,6 +486,10 @@
         return mGenericLink;
     }
 
+    UserHandle getUser() {
+        return mUserContext.getUser();
+    }
+
     private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
         if (!isDragResizable(mTaskInfo)) {
             if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
@@ -569,7 +579,8 @@
             return new AppHandleViewHolder(
                     mResult.mRootView,
                     mOnCaptionTouchListener,
-                    mOnCaptionButtonClickListener
+                    mOnCaptionButtonClickListener,
+                    mWindowManagerWrapper
             );
         } else if (mRelayoutParams.mLayoutResId
                 == R.layout.desktop_mode_app_header) {
@@ -983,6 +994,7 @@
         updateGenericLink();
         mHandleMenu = mHandleMenuFactory.create(
                 this,
+                mWindowManagerWrapper,
                 mRelayoutParams.mLayoutResId,
                 mAppIconBitmap,
                 mAppName,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 34de94e..748046e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -64,6 +64,7 @@
  */
 class HandleMenu(
     private val parentDecor: DesktopModeWindowDecoration,
+    private val windowManagerWrapper: WindowManagerWrapper,
     private val layoutResId: Int,
     private val appIconBitmap: Bitmap?,
     private val appName: CharSequence?,
@@ -178,7 +179,7 @@
         handleMenuViewContainer =
             if (!taskInfo.isFreeform && Flags.enableAdditionalWindowsAboveStatusBar()) {
                 AdditionalSystemViewContainer(
-                    context = context,
+                    windowManagerWrapper = windowManagerWrapper,
                     taskId = taskInfo.taskId,
                     x = x,
                     y = y,
@@ -635,6 +636,7 @@
 interface HandleMenuFactory {
     fun create(
         parentDecor: DesktopModeWindowDecoration,
+        windowManagerWrapper: WindowManagerWrapper,
         layoutResId: Int,
         appIconBitmap: Bitmap?,
         appName: CharSequence?,
@@ -652,6 +654,7 @@
 object DefaultHandleMenuFactory : HandleMenuFactory {
     override fun create(
         parentDecor: DesktopModeWindowDecoration,
+        windowManagerWrapper: WindowManagerWrapper,
         layoutResId: Int,
         appIconBitmap: Bitmap?,
         appName: CharSequence?,
@@ -665,6 +668,7 @@
     ): HandleMenu {
         return HandleMenu(
             parentDecor,
+            windowManagerWrapper,
             layoutResId,
             appIconBitmap,
             appName,
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 114c331..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
@@ -318,7 +324,7 @@
             rootView.setOnTouchListener { _, event ->
                 if (event.actionMasked == ACTION_OUTSIDE) {
                     onOutsideTouchListener?.invoke()
-                    false
+                    return@setOnTouchListener false
                 }
                 true
             }
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/src/com/android/wm/shell/windowdecor/WindowManagerWrapper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowManagerWrapper.kt
new file mode 100644
index 0000000..5c2ff1b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowManagerWrapper.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor
+
+import android.view.View
+import android.view.WindowManager
+
+/**
+ * A wrapper for [WindowManager] to make view manipulation operations related to window
+ * decors more testable.
+ */
+class WindowManagerWrapper (
+    private val windowManager: WindowManager
+){
+
+    fun addView(v: View, lp: WindowManager.LayoutParams) {
+        windowManager.addView(v, lp)
+    }
+
+    fun removeViewImmediate(v: View) {
+        windowManager.removeViewImmediate(v)
+    }
+
+    fun updateViewLayout(v: View, lp: WindowManager.LayoutParams) {
+        windowManager.updateViewLayout(v, lp)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index cadd80e..226b0fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -24,13 +24,14 @@
 import android.view.SurfaceControl
 import android.view.View
 import android.view.WindowManager
+import com.android.wm.shell.windowdecor.WindowManagerWrapper
 
 /**
  * An [AdditionalViewContainer] that uses the system [WindowManager] instance. Intended
  * for view containers that should be above the status bar layer.
  */
 class AdditionalSystemViewContainer(
-    context: Context,
+    private val windowManagerWrapper: WindowManagerWrapper,
     taskId: Int,
     x: Int,
     y: Int,
@@ -39,9 +40,20 @@
     flags: Int,
     override val view: View
 ) : AdditionalViewContainer() {
+    val lp: WindowManager.LayoutParams = WindowManager.LayoutParams(
+        width, height, x, y,
+        WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
+        flags,
+        PixelFormat.TRANSPARENT
+    ).apply {
+        title = "Additional view container of Task=$taskId"
+        gravity = Gravity.LEFT or Gravity.TOP
+        setTrustedOverlay()
+    }
 
     constructor(
         context: Context,
+        windowManagerWrapper: WindowManagerWrapper,
         taskId: Int,
         x: Int,
         y: Int,
@@ -50,7 +62,7 @@
         flags: Int,
         @LayoutRes layoutId: Int
     ) : this(
-        context = context,
+        windowManagerWrapper = windowManagerWrapper,
         taskId = taskId,
         x = x,
         y = y,
@@ -61,9 +73,16 @@
     )
 
     constructor(
-        context: Context, taskId: Int, x: Int, y: Int, width: Int, height: Int, flags: Int
+        context: Context,
+        windowManagerWrapper: WindowManagerWrapper,
+        taskId: Int,
+        x: Int,
+        y: Int,
+        width: Int,
+        height: Int,
+        flags: Int
     ) : this(
-        context = context,
+        windowManagerWrapper = windowManagerWrapper,
         taskId = taskId,
         x = x,
         y = y,
@@ -73,24 +92,12 @@
         view = View(context)
     )
 
-    val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java)
-
     init {
-        val lp = WindowManager.LayoutParams(
-            width, height, x, y,
-            WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
-            flags,
-            PixelFormat.TRANSPARENT
-        ).apply {
-            title = "Additional view container of Task=$taskId"
-            gravity = Gravity.LEFT or Gravity.TOP
-            setTrustedOverlay()
-        }
-        windowManager?.addView(view, lp)
+        windowManagerWrapper.addView(view, lp)
     }
 
     override fun releaseView() {
-        windowManager?.removeViewImmediate(view)
+        windowManagerWrapper.removeViewImmediate(view)
     }
 
     override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) {
@@ -98,6 +105,6 @@
             this.x = x.toInt()
             this.y = y.toInt()
         }
-        windowManager?.updateViewLayout(view, lp)
+        windowManagerWrapper.updateViewLayout(view, lp)
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 510032b..dfa5ab4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -32,6 +32,7 @@
 import com.android.window.flags.Flags
 import com.android.wm.shell.R
 import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.windowdecor.WindowManagerWrapper
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 
 /**
@@ -41,14 +42,14 @@
 internal class AppHandleViewHolder(
     rootView: View,
     onCaptionTouchListener: View.OnTouchListener,
-    onCaptionButtonClickListener: OnClickListener
+    onCaptionButtonClickListener: OnClickListener,
+    private val windowManagerWrapper: WindowManagerWrapper
 ) : WindowDecorationViewHolder(rootView) {
 
     companion object {
         private const val CAPTION_HANDLE_ANIMATION_DURATION: Long = 100
     }
     private lateinit var taskInfo: RunningTaskInfo
-    private val windowManager = context.getSystemService(WindowManager::class.java)
     private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
     private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
     private val inputManager = context.getSystemService(InputManager::class.java)
@@ -96,11 +97,12 @@
                                           handleWidth: Int,
                                           handleHeight: Int) {
         if (!Flags.enableAdditionalWindowsAboveStatusBar()) return
-        statusBarInputLayer = AdditionalSystemViewContainer(context, taskInfo.taskId,
-            handlePosition.x, handlePosition.y, handleWidth, handleHeight,
+        statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper,
+            taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
             WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
-        val view = statusBarInputLayer?.view
-        val lp = view?.layoutParams as WindowManager.LayoutParams
+        val view = statusBarInputLayer?.view ?: error("Unable to find statusBarInputLayer View")
+        val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer" +
+                "LayoutParams")
         lp.title = "Handle Input Layer of task " + taskInfo.taskId
         lp.setTrustedOverlay()
         // Make this window a spy window to enable it to pilfer pointers from the system-wide
@@ -120,7 +122,7 @@
             captionHandle.dispatchTouchEvent(event)
             true
         }
-        windowManager.updateViewLayout(view, lp)
+        windowManagerWrapper.updateViewLayout(view, lp)
     }
 
     private fun updateStatusBarInputLayer(globalPosition: Point) {
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 8584b59..880e021 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,21 +17,32 @@
 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
+import android.tools.flicker.assertors.assertions.AppWindowMaintainsAspectRatioAlways
 import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
 import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
+import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
+import android.tools.flicker.assertors.assertions.AppWindowReturnsToStartBoundsAndPosition
 import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow
 import android.tools.flicker.config.AssertionTemplates
 import android.tools.flicker.config.FlickerConfigEntry
 import android.tools.flicker.config.ScenarioId
-import android.tools.flicker.config.desktopmode.Components
+import android.tools.flicker.config.desktopmode.Components.DESKTOP_MODE_APP
 import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER
+import android.tools.flicker.config.desktopmode.Components.NON_RESIZABLE_APP
 import android.tools.flicker.extractors.ITransitionMatcher
 import android.tools.flicker.extractors.ShellTransitionScenarioExtractor
 import android.tools.flicker.extractors.TaggedCujTransitionMatcher
@@ -46,29 +57,27 @@
             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(Components.DESKTOP_MODE_APP),
-                                AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
-                                AppWindowHasDesktopModeInitialBoundsAtTheEnd(
-                                    Components.DESKTOP_MODE_APP
-                                ),
-                                AppWindowBecomesVisible(DESKTOP_WALLPAPER)
-                            )
+                            AppLayerIsVisibleAlways(DESKTOP_MODE_APP),
+                            AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+                            AppWindowHasDesktopModeInitialBoundsAtTheEnd(DESKTOP_MODE_APP),
+                            AppWindowBecomesVisible(DESKTOP_WALLPAPER)
+                        )
                             .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
@@ -78,57 +87,56 @@
             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(Components.DESKTOP_MODE_APP),
-                                AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP),
-                                AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
-                            )
-                            .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+                            AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+                            AppLayerIsVisibleAtStart(DESKTOP_MODE_APP),
+                            AppLayerIsInvisibleAtEnd(DESKTOP_MODE_APP)
+                        ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
         val CLOSE_LAST_APP =
             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(Components.DESKTOP_MODE_APP),
-                                LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP),
-                                AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
-                            )
+                            AppWindowIsInvisibleAtEnd(DESKTOP_MODE_APP),
+                            LauncherWindowReplacesAppAsTopWindow(DESKTOP_MODE_APP),
+                            AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
+                        )
                             .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
@@ -136,29 +144,161 @@
             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
             )
 
+        val EDGE_RESIZE =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("EDGE_RESIZE"),
+                extractor =
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+                        listOf(
+                            AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+                        ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
+
         val CORNER_RESIZE_TO_MINIMUM_SIZE =
             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(Components.DESKTOP_MODE_APP, 770, 700))
+                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"),
+                extractor =
+                TaggedScenarioExtractorBuilder()
+                    .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+                    .setTransitionMatcher(
+                        TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                    )
+                    .build(),
+                assertions = listOf(
+                    AppWindowIsVisibleAlways(NON_RESIZABLE_APP),
+                    AppWindowOnTopAtEnd(NON_RESIZABLE_APP),
+                    AppWindowRemainInsideDisplayBounds(NON_RESIZABLE_APP),
+                    AppWindowMaintainsAspectRatioAlways(NON_RESIZABLE_APP),
+                    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/ResizeAppWithEdgeResizeMouse.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithEdgeResizeMouse.kt
new file mode 100644
index 0000000..c3abf23
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithEdgeResizeMouse.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.server.wm.flicker.helpers.MotionEventHelper.InputMethod
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.EDGE_RESIZE
+import com.android.wm.shell.scenarios.ResizeAppWithEdgeResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppWithEdgeResizeMouse : ResizeAppWithEdgeResize(InputMethod.MOUSE) {
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeRight() = super.resizeAppWithEdgeResizeRight()
+
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeLeft() = super.resizeAppWithEdgeResizeLeft()
+
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeTop() = super.resizeAppWithEdgeResizeTop()
+
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeBottom() = super.resizeAppWithEdgeResizeBottom()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(EDGE_RESIZE)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithEdgeResizeStylus.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithEdgeResizeStylus.kt
new file mode 100644
index 0000000..86b0e6f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithEdgeResizeStylus.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.server.wm.flicker.helpers.MotionEventHelper.InputMethod
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.EDGE_RESIZE
+import com.android.wm.shell.scenarios.ResizeAppWithEdgeResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppWithEdgeResizeStylus : ResizeAppWithEdgeResize(InputMethod.STYLUS) {
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeRight() = super.resizeAppWithEdgeResizeRight()
+
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeLeft() = super.resizeAppWithEdgeResizeLeft()
+
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeTop() = super.resizeAppWithEdgeResizeTop()
+
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeBottom() = super.resizeAppWithEdgeResizeBottom()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(EDGE_RESIZE)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithEdgeResizeTouchpad.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithEdgeResizeTouchpad.kt
new file mode 100644
index 0000000..e6bb9ef
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppWithEdgeResizeTouchpad.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.server.wm.flicker.helpers.MotionEventHelper.InputMethod
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.EDGE_RESIZE
+import com.android.wm.shell.scenarios.ResizeAppWithEdgeResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppWithEdgeResizeTouchpad : ResizeAppWithEdgeResize(InputMethod.TOUCHPAD) {
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeRight() = super.resizeAppWithEdgeResizeRight()
+
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeLeft() = super.resizeAppWithEdgeResizeLeft()
+
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeTop() = super.resizeAppWithEdgeResizeTop()
+
+    @ExpectedScenarios(["EDGE_RESIZE"])
+    @Test
+    override fun resizeAppWithEdgeResizeBottom() = super.resizeAppWithEdgeResizeBottom()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(EDGE_RESIZE)
+    }
+}
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/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.kt
new file mode 100644
index 0000000..582658f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowLeftWithDrag.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.SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize non-resizable app window by dragging it to the left edge of the screen.
+ *
+ * Assert that the app window keeps the same size and returns to its original pre-drag position.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeNonResizableAppWindowLeftWithDrag :
+    SnapResizeAppWindowWithDrag(toLeft = true, isResizable = false) {
+    @ExpectedScenarios(["SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE"])
+    @Test
+    override fun snapResizeAppWindowWithDrag() = super.snapResizeAppWindowWithDrag()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+                .use(SNAP_RESIZE_WITH_DRAG_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/SnapResizeNonResizableAppWindowRightWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowRightWithDrag.kt
new file mode 100644
index 0000000..7205ec4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeNonResizableAppWindowRightWithDrag.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.SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize non-resizable app window by dragging it to the right edge of the screen.
+ *
+ * Assert that the app window keeps the same size and returns to its original pre-drag position.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeNonResizableAppWindowRightWithDrag :
+    SnapResizeAppWindowWithDrag(toLeft = false, isResizable = false) {
+    @ExpectedScenarios(["SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE"])
+    @Test
+    override fun snapResizeAppWindowWithDrag() = super.snapResizeAppWindowWithDrag()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+                .use(SNAP_RESIZE_WITH_DRAG_NON_RESIZABLE)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DesktopScenarioCustomAppTestBase.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DesktopScenarioCustomAppTestBase.kt
new file mode 100644
index 0000000..5a69b27
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DesktopScenarioCustomAppTestBase.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.Ignore
+
+/** Base test class for desktop CUJ with customizable test app. */
+@Ignore("Base Test Class")
+abstract class DesktopScenarioCustomAppTestBase(
+    isResizeable: Boolean = true,
+    isLandscapeApp: Boolean = true
+) {
+    val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    val tapl = LauncherInstrumentation()
+    val wmHelper = WindowManagerStateHelper(instrumentation)
+    val device = UiDevice.getInstance(instrumentation)
+    // TODO(b/363181411): Consolidate in LetterboxAppHelper.
+    val testApp = when {
+        isResizeable && isLandscapeApp -> SimpleAppHelper(instrumentation)
+        isResizeable && !isLandscapeApp -> SimpleAppHelper(
+                instrumentation,
+                launcherName = ActivityOptions.PortraitOnlyActivity.LABEL,
+                component = ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent()
+            )
+        // NonResizeablAppHelper has no fixed orientation.
+        !isResizeable && isLandscapeApp -> NonResizeableAppHelper(instrumentation)
+        // Opens NonResizeablePortraitActivity.
+        else -> LetterboxAppHelper(instrumentation)
+    }.let { DesktopModeAppHelper(it) }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
index 0f0d2df..5f759e8 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
@@ -17,15 +17,8 @@
 package com.android.wm.shell.scenarios
 
 import android.platform.test.annotations.Postsubmit
-import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.traces.parsers.WindowManagerStateHelper
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.window.flags.Flags
 import com.android.wm.shell.Utils
 import org.junit.After
@@ -40,13 +33,11 @@
 @Postsubmit
 open class EnterDesktopWithDrag
 @JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0) {
-
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val tapl = LauncherInstrumentation()
-    private val wmHelper = WindowManagerStateHelper(instrumentation)
-    private val device = UiDevice.getInstance(instrumentation)
-    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+constructor(
+    val rotation: Rotation = Rotation.ROTATION_0,
+    isResizeable: Boolean = true,
+    isLandscapeApp: Boolean = true
+) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) {
 
     @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index 533be88..b616e53 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -17,15 +17,8 @@
 package com.android.wm.shell.scenarios
 
 import android.platform.test.annotations.Postsubmit
-import android.app.Instrumentation
 import android.tools.NavBar
 import android.tools.Rotation
-import android.tools.traces.parsers.WindowManagerStateHelper
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
-import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.window.flags.Flags
 import com.android.wm.shell.Utils
 import org.junit.After
@@ -40,13 +33,11 @@
 @Postsubmit
 open class ExitDesktopWithDragToTopDragZone
 @JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0) {
-
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
-    private val tapl = LauncherInstrumentation()
-    private val wmHelper = WindowManagerStateHelper(instrumentation)
-    private val device = UiDevice.getInstance(instrumentation)
-    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+constructor(
+    val rotation: Rotation = Rotation.ROTATION_0,
+    isResizeable: Boolean = true,
+    isLandscapeApp: Boolean = true
+) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) {
 
     @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
index e3660fe..426f40b 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindow.kt
@@ -16,15 +16,17 @@
 
 package com.android.wm.shell.scenarios
 
-import android.platform.test.annotations.Postsubmit
 import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
 import android.tools.NavBar
 import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
 import android.tools.traces.parsers.WindowManagerStateHelper
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.window.flags.Flags
 import com.android.wm.shell.Utils
@@ -35,22 +37,31 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.BlockJUnit4ClassRunner
+
 @RunWith(BlockJUnit4ClassRunner::class)
 @Postsubmit
 open class MaximizeAppWindow
-{
+@JvmOverloads
+constructor(private val rotation: Rotation = Rotation.ROTATION_0, isResizable: Boolean = true) {
+
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val tapl = LauncherInstrumentation()
     private val wmHelper = WindowManagerStateHelper(instrumentation)
     private val device = UiDevice.getInstance(instrumentation)
-    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val testApp = if (isResizable) {
+        DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    } else {
+        DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    }
 
-    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL,
-        Rotation.ROTATION_0)
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
 
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        ChangeDisplayOrientationRule.setRotation(rotation)
         testApp.enterDesktopWithDrag(wmHelper, device)
     }
 
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
index 63e7387..42940a9 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt
@@ -25,6 +25,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.window.flags.Flags
 import com.android.wm.shell.Utils
@@ -40,15 +41,24 @@
 @Postsubmit
 open class ResizeAppWithCornerResize
 @JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0,
-    val horizontalChange: Int = 50,
-    val verticalChange: Int = -50) {
+constructor(
+    val rotation: Rotation = Rotation.ROTATION_0,
+    val horizontalChange: Int = 200,
+    val verticalChange: Int = -200,
+    val appProperty: AppProperty = AppProperty.STANDARD
+) {
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val tapl = LauncherInstrumentation()
     private val wmHelper = WindowManagerStateHelper(instrumentation)
     private val device = UiDevice.getInstance(instrumentation)
-    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val testApp =
+        DesktopModeAppHelper(
+            when (appProperty) {
+                AppProperty.STANDARD -> SimpleAppHelper(instrumentation)
+                AppProperty.NON_RESIZABLE -> NonResizeableAppHelper(instrumentation)
+            }
+        )
 
     @Rule
     @JvmField
@@ -64,15 +74,24 @@
 
     @Test
     open fun resizeAppWithCornerResize() {
-        testApp.cornerResize(wmHelper,
+        testApp.cornerResize(
+            wmHelper,
             device,
             DesktopModeAppHelper.Corners.RIGHT_TOP,
             horizontalChange,
-            verticalChange)
+            verticalChange
+        )
     }
 
     @After
     fun teardown() {
         testApp.exit(wmHelper)
     }
+
+    companion object {
+        enum class AppProperty {
+            STANDARD,
+            NON_RESIZABLE
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
new file mode 100644
index 0000000..d094967
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.MotionEventHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ResizeAppWithEdgeResize
+@JvmOverloads
+constructor(
+    val inputMethod: MotionEventHelper.InputMethod,
+    val rotation: Rotation = Rotation.ROTATION_90
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val motionEventHelper = MotionEventHelper(instrumentation, inputMethod)
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(
+            Flags.enableDesktopWindowingMode()
+                    && Flags.enableWindowingEdgeDragResize() && tapl.isTablet
+        )
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+    }
+
+    @Test
+    open fun resizeAppWithEdgeResizeRight() {
+        testApp.edgeResize(
+            wmHelper,
+            motionEventHelper,
+            DesktopModeAppHelper.Edges.RIGHT
+        )
+    }
+
+    @Test
+    open fun resizeAppWithEdgeResizeLeft() {
+        testApp.edgeResize(
+            wmHelper,
+            motionEventHelper,
+            DesktopModeAppHelper.Edges.LEFT
+        )
+    }
+
+    @Test
+    open fun resizeAppWithEdgeResizeTop() {
+        testApp.edgeResize(
+            wmHelper,
+            motionEventHelper,
+            DesktopModeAppHelper.Edges.TOP
+        )
+    }
+
+    @Test
+    open fun resizeAppWithEdgeResizeBottom() {
+        testApp.edgeResize(
+            wmHelper,
+            motionEventHelper,
+            DesktopModeAppHelper.Edges.BOTTOM
+        )
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
new file mode 100644
index 0000000..33242db
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt
@@ -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.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class SnapResizeAppWindowWithButton
+@JvmOverloads
+constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = if (isResizable) {
+        DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    } else {
+        DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    }
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+    }
+
+    @Test
+    open fun snapResizeAppWindowWithButton() {
+        testApp.snapResizeDesktopApp(wmHelper, device, instrumentation.context, toLeft)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
new file mode 100644
index 0000000..14eb779
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt
@@ -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.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class SnapResizeAppWindowWithDrag
+@JvmOverloads
+constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = if (isResizable) {
+        DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    } else {
+        DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    }
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+    }
+
+    @Test
+    open fun snapResizeAppWindowWithDrag() {
+        testApp.dragToSnapResizeRegion(wmHelper, device, toLeft)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt
index dee67f3..c0fafef 100644
--- a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt
+++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/Utils.kt
@@ -17,6 +17,7 @@
 package com.android.wm.shell
 
 import android.app.Instrumentation
+import android.platform.test.rule.EnsureDeviceSettingsRule
 import android.platform.test.rule.NavigationModeRule
 import android.platform.test.rule.PressHomeRule
 import android.platform.test.rule.UnlockScreenRule
@@ -49,5 +50,6 @@
                 )
             )
             .around(PressHomeRule())
+            .around(EnsureDeviceSettingsRule())
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 4abaf5b..7305f49 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -68,6 +68,7 @@
         "flickerlib-helpers",
         "flickerlib-trace_processor_shell",
         "platform-test-annotations",
+        "platform-test-rules",
         "wm-flicker-common-app-helpers",
         "wm-flicker-common-assertions",
         "launcher-helper-lib",
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/OWNERS b/libs/WindowManager/Shell/tests/flicker/appcompat/OWNERS
new file mode 100644
index 0000000..a36a4f8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/OWNERS
@@ -0,0 +1,2 @@
+# Window Manager > App Compat
+# Bug component: 970984
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index b85d793..a9ed13a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -16,10 +16,14 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder
+import android.tools.flicker.subject.exceptions.IncorrectRegionException
+import android.tools.flicker.subject.layers.LayerSubject
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.pip.common.EnterPipTransition
@@ -29,6 +33,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
+import kotlin.math.abs
 
 /**
  * Test entering pip from an app via auto-enter property when navigating to home.
@@ -67,9 +72,24 @@
 
     override val defaultTeardown: FlickerBuilder.() -> Unit = { teardown { pipApp.exit(wmHelper) } }
 
-    @FlakyTest(bugId = 293133362)
+    private val widthNotSmallerThan: LayerSubject.(LayerSubject) -> Unit = {
+        val width = visibleRegion.region.bounds.width()
+        val otherWidth = it.visibleRegion.region.bounds.width()
+        if (width < otherWidth && abs(width - otherWidth) > EPSILON) {
+            val errorMsgBuilder =
+                ExceptionMessageBuilder()
+                    .forSubject(this)
+                    .forIncorrectRegion("width. $width smaller than $otherWidth")
+                    .setExpected(width)
+                    .setActual(otherWidth)
+            throw IncorrectRegionException(errorMsgBuilder)
+        }
+    }
+
+    @Postsubmit
     @Test
     override fun pipLayerReduces() {
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
         flicker.assertLayers {
             val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
             pipLayerList.zipWithNext { previous, current ->
@@ -78,6 +98,32 @@
         }
     }
 
+    /** Checks that [pipApp] window's width is first decreasing then increasing. */
+    @Postsubmit
+    @Test
+    fun pipLayerWidthDecreasesThenIncreases() {
+        Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+        flicker.assertLayers {
+            val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+            var previousLayer = pipLayerList[0]
+            var currentLayer = previousLayer
+            var i = 0
+            invoke("layer area is decreasing") {
+                if (i < pipLayerList.size - 1) {
+                    previousLayer = currentLayer
+                    currentLayer = pipLayerList[++i]
+                    previousLayer.widthNotSmallerThan(currentLayer)
+                }
+            }.then().invoke("layer are is increasing", true /* isOptional */) {
+                if (i < pipLayerList.size - 1) {
+                    previousLayer = currentLayer
+                    currentLayer = pipLayerList[++i]
+                    currentLayer.widthNotSmallerThan(previousLayer)
+                }
+            }
+        }
+    }
+
     /** Checks that [pipApp] window is animated towards default position in right bottom corner */
     @FlakyTest(bugId = 255578530)
     @Test
@@ -108,4 +154,9 @@
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
     }
+
+    companion object {
+        // TODO(b/363080056): A margin of error allowed on certain layer size calculations.
+        const val EPSILON = 1
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
index 70be58f..429774f 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/PipAspectRatioChangeTest.kt
@@ -35,7 +35,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class PipAspectRatioChangeTest(flicker: LegacyFlickerTest) : PipTransition(flicker) {
     override val thisTransition: FlickerBuilder.() -> Unit = {
-        transitions { pipApp.changeAspectRatio() }
+        transitions { pipApp.changeAspectRatio(wmHelper) }
     }
 
     @Presubmit
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/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 4d761e1..006a4a9 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -94,3 +94,10 @@
         "com.android.wm.shell.tests",
     ],
 }
+
+test_module_config {
+    name: "WMShellUnitTests_shell_back",
+    base: "WMShellUnitTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.wm.shell.back"],
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index 4998702..f31722d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -19,6 +19,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Really basic test executor. It just gathers all events in a blob. The only option is to
@@ -52,4 +53,9 @@
             mRunnables.remove(0).run();
         }
     }
+
+    /** Returns the list of callbacks for this executor. */
+    public List<Runnable> getCallbacks() {
+        return mRunnables;
+    }
 }
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/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 859602e..6fa3788 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -50,8 +50,8 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.bubbles.BubbleData.TimeSource;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
-import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
 
 import com.google.common.collect.ImmutableList;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index 50c4a18..dca5fc4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -43,7 +43,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.bubbles.BubbleInfo;
+import com.android.wm.shell.shared.bubbles.BubbleInfo;
 
 import org.junit.Before;
 import org.junit.Test;
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/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 70b3661..ca97229 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -104,7 +104,9 @@
                 /* session_id */
                 eq(SESSION_ID),
                 eq(UNSET_MINIMIZE_REASON),
-                eq(UNSET_UNMINIMIZE_REASON))
+                eq(UNSET_UNMINIMIZE_REASON),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -131,7 +133,9 @@
                 /* session_id */
                 eq(SESSION_ID),
                 eq(UNSET_MINIMIZE_REASON),
-                eq(UNSET_UNMINIMIZE_REASON))
+                eq(UNSET_UNMINIMIZE_REASON),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -159,7 +163,9 @@
                 /* session_id */
                 eq(SESSION_ID),
                 eq(UNSET_MINIMIZE_REASON),
-                eq(UNSET_UNMINIMIZE_REASON))
+                eq(UNSET_UNMINIMIZE_REASON),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -190,7 +196,9 @@
                 /* minimize_reason */
                 eq(MinimizeReason.TASK_LIMIT.reason),
                 /* unminimize_reason */
-                eq(UNSET_UNMINIMIZE_REASON))
+                eq(UNSET_UNMINIMIZE_REASON),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -221,7 +229,9 @@
                 /* minimize_reason */
                 eq(UNSET_MINIMIZE_REASON),
                 /* unminimize_reason */
-                eq(UnminimizeReason.TASKBAR_TAP.reason))
+                eq(UnminimizeReason.TASKBAR_TAP.reason),
+                /* visible_task_count */
+                eq(TASK_COUNT))
         }
     }
 
@@ -233,15 +243,17 @@
         private const val TASK_Y = 0
         private const val TASK_HEIGHT = 100
         private const val TASK_WIDTH = 100
+        private const val TASK_COUNT = 1
 
         private val TASK_UPDATE = TaskUpdate(
-            TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y
+            TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
+            visibleTaskCount = TASK_COUNT,
         )
 
         private fun createTaskUpdate(
             minimizeReason: MinimizeReason? = null,
             unminimizeReason: UnminimizeReason? = null,
         ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
-            unminimizeReason)
+            unminimizeReason, TASK_COUNT)
     }
 }
\ 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 74fc7b0..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>()
@@ -138,7 +145,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -152,7 +160,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -165,7 +174,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -178,7 +188,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -191,7 +202,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -201,7 +213,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -231,7 +244,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -261,7 +275,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -291,7 +306,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -324,7 +340,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -335,7 +352,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -345,7 +363,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -364,7 +383,8 @@
 
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -386,7 +406,8 @@
             .build()
     callOnTransitionReady(transitionInfo)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -522,7 +543,8 @@
     val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
     callOnTransitionReady(transitionInfo2)
 
-    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW, DEFAULT_TASK_UPDATE)
+    verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
+        DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
   }
 
   @Test
@@ -538,7 +560,8 @@
     callOnTransitionReady(transitionInfo)
 
     verify(desktopModeEventLogger, times(1))
-        .logTaskAdded(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
+        .logTaskAdded(eq(sessionId),
+            eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2)))
     verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
   }
 
@@ -560,7 +583,8 @@
 
     verify(desktopModeEventLogger, times(1))
         .logTaskInfoChanged(
-            eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100)))
+            eq(sessionId),
+            eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1)))
     verifyZeroInteractions(desktopModeEventLogger)
   }
 
@@ -589,7 +613,8 @@
             eq(sessionId),
             eq(
                 DEFAULT_TASK_UPDATE.copy(
-                    taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100)))
+                    taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100,
+                    visibleTaskCount = 1)))
     verifyZeroInteractions(desktopModeEventLogger)
   }
 
@@ -613,7 +638,8 @@
 
     verify(desktopModeEventLogger, times(1))
         .logTaskInfoChanged(
-            eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100)))
+            eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(
+                taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2)))
     verifyZeroInteractions(desktopModeEventLogger)
 
     // task 2 resize
@@ -637,7 +663,9 @@
                 DEFAULT_TASK_UPDATE.copy(
                     instanceId = 2,
                     taskWidth = DEFAULT_TASK_WIDTH + 100,
-                    taskHeight = DEFAULT_TASK_HEIGHT - 100)))
+                    taskHeight = DEFAULT_TASK_HEIGHT - 100,
+                    visibleTaskCount = 2)),
+            )
     verifyZeroInteractions(desktopModeEventLogger)
   }
 
@@ -655,7 +683,8 @@
     callOnTransitionReady(transitionInfo)
 
     verify(desktopModeEventLogger, times(1))
-        .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2)))
+        .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy(
+            instanceId = 2, visibleTaskCount = 1)))
     verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
   }
 
@@ -673,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)
   }
 
@@ -694,6 +734,7 @@
     const val DEFAULT_TASK_WIDTH = 200
     const val DEFAULT_TASK_X = 30
     const val DEFAULT_TASK_Y = 70
+    const val DEFAULT_VISIBLE_TASK_COUNT = 0
     val DEFAULT_TASK_UPDATE =
         TaskUpdate(
             DEFAULT_TASK_ID,
@@ -702,6 +743,7 @@
             DEFAULT_TASK_WIDTH,
             DEFAULT_TASK_X,
             DEFAULT_TASK_Y,
+            visibleTaskCount = DEFAULT_VISIBLE_TASK_COUNT,
         )
 
     fun createTaskInfo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
index 518c00d..db4e93d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
@@ -18,11 +18,6 @@
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.TASK_DRAG
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
@@ -33,6 +28,11 @@
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getEnterTransitionType
 import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getExitTransitionType
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
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 2e0af273..10557dd 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
@@ -83,8 +83,8 @@
 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.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
 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
@@ -94,6 +94,7 @@
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
 import com.android.wm.shell.shared.split.SplitScreenConstants
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
@@ -122,12 +123,12 @@
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.atLeastOnce
 import org.mockito.kotlin.eq
@@ -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)
@@ -1515,8 +1565,34 @@
     assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
         .isEqualTo(WINDOWING_MODE_FREEFORM)
 
-    assertThat(wct.hierarchyOps).hasSize(2)
-    wct.assertReorderAt(1, homeTask, toTop = false)
+    assertThat(wct.hierarchyOps).hasSize(1)
+  }
+
+  @Test
+  fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+    val homeTask = setUpHomeTask()
+    val freeformTask = setUpFreeformTask()
+    markTaskVisible(freeformTask)
+    val fullscreenTask = createFullscreenTask()
+    fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+    val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+    assertNotNull(wct, "should handle request")
+    assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+      .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+    // There are 5 hops that are happening in this case:
+    // 1. Moving the fullscreen task to top as we add moveToDesktop() changes
+    // 2. Bringing home task to front
+    // 3. Pending intent for the wallpaper
+    // 4. Bringing the existing freeform task to top
+    // 5. Bringing the fullscreen task back at the top
+    assertThat(wct.hierarchyOps).hasSize(5)
+    wct.assertReorderAt(1, homeTask, toTop = true)
+    wct.assertReorderAt(4, fullscreenTask, toTop = true)
   }
 
   @Test
@@ -1541,19 +1617,34 @@
     val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
     freeformTasks.forEach { markTaskVisible(it) }
     val fullscreenTask = createFullscreenTask()
-    val homeTask = setUpHomeTask(DEFAULT_DISPLAY)
 
     val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
 
     // Make sure we reorder the new task to top, and the back task to the bottom
-    assertThat(wct!!.hierarchyOps.size).isEqualTo(3)
+    assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
     wct.assertReorderAt(0, fullscreenTask, toTop = true)
-    wct.assertReorderAt(1, homeTask, toTop = false)
-    wct.assertReorderAt(2, freeformTasks[0], toTop = false)
+    wct.assertReorderAt(1, freeformTasks[0], toTop = false)
   }
 
   @Test
-  fun handleRequest_fullscreenTaskToFreeform_alreadyBeyondLimit_existingAndNewTasksAreMinimized() {
+  fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+    val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+    freeformTasks.forEach { markTaskVisible(it) }
+    val fullscreenTask = createFullscreenTask()
+    fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+    val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+    // Make sure we reorder the new task to top, and the back task to the bottom
+    assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
+    wct.assertReorderAt(0, fullscreenTask, toTop = true)
+    wct.assertReorderAt(8, freeformTasks[0], toTop = false)
+  }
+
+  @Test
+  fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() {
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
 
     val minimizedTask = setUpFreeformTask()
@@ -1562,15 +1653,16 @@
     freeformTasks.forEach { markTaskVisible(it) }
     val homeTask = setUpHomeTask()
     val fullscreenTask = createFullscreenTask()
+    fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
 
     val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
 
-    assertThat(wct!!.hierarchyOps.size).isEqualTo(4)
+    assertThat(wct!!.hierarchyOps.size).isEqualTo(10)
     wct.assertReorderAt(0, fullscreenTask, toTop = true)
-    // Make sure we reorder the home task to the bottom, and minimized tasks below the home task.
-    wct.assertReorderAt(1, homeTask, toTop = false)
-    wct.assertReorderAt(2, minimizedTask, toTop = false)
-    wct.assertReorderAt(3, freeformTasks[0], toTop = false)
+    // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
+    // task is under the home task.
+    wct.assertReorderAt(1, homeTask, toTop = true)
+    wct.assertReorderAt(9, freeformTasks[0], toTop = false)
   }
 
   @Test
@@ -2767,7 +2859,7 @@
   }
 
   @Test
-  fun getSnapBounds_calculatesBoundsForResizable() {
+  fun snapToHalfScreen_getSnapBounds_calculatesBoundsForResizable() {
     val bounds = Rect(100, 100, 300, 300)
     val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
       topActivityInfo = ActivityInfo().apply {
@@ -2782,13 +2874,45 @@
       STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
     )
 
-    controller.snapToHalfScreen(task, currentDragBounds, SnapPosition.LEFT)
+    controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)
     // Assert bounds set to stable bounds
     val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
     assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
   }
 
   @Test
+  fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() {
+    assumeTrue(ENABLE_SHELL_TRANSITIONS)
+    // Set up task to already be in snapped-left bounds
+    val bounds = Rect(
+      STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
+    )
+    val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+      topActivityInfo = ActivityInfo().apply {
+        screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+        configuration.windowConfiguration.appBounds = bounds
+      }
+      isResizeable = true
+    }
+
+    // Attempt to snap left again
+    val currentDragBounds = Rect(bounds).apply { offset(-100, 0) }
+    controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT)
+
+    // Assert that task is NOT updated via WCT
+    verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+
+    // Assert that task leash is updated via Surface Animations
+    verify(mReturnToDragStartAnimator).start(
+      eq(task.taskId),
+      eq(mockSurface),
+      eq(currentDragBounds),
+      eq(bounds),
+      eq(true)
+    )
+  }
+
+  @Test
   @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
   fun handleSnapResizingTask_nonResizable_snapsToHalfScreen() {
     val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply {
@@ -2819,7 +2943,8 @@
       eq(task.taskId),
       eq(mockSurface),
       eq(currentDragBounds),
-      eq(preDragBounds)
+      eq(preDragBounds),
+      eq(false)
     )
   }
 
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/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index e5157c9..e0463b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -48,7 +48,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index 6736593..0c3f98a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -24,13 +24,13 @@
 import android.window.WindowContainerToken
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
+import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
+import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.GroupedRecentTaskInfo.CREATOR
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SINGLE
-import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SPLIT
-import com.android.wm.shell.util.SplitBounds
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index e1fe4e9..386253c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -46,6 +46,7 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -68,13 +69,13 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.GroupedRecentTaskInfo;
 import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitBounds;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.SplitBounds;
 
 import org.junit.After;
 import org.junit.Before;
@@ -136,6 +137,8 @@
 
         mMainExecutor = new TestShellExecutor();
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
+        when(mContext.getSystemService(KeyguardManager.class))
+                .thenReturn(mock(KeyguardManager.class));
         mShellInit = spy(new ShellInit(mMainExecutor));
         mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
                 mDisplayInsetsController, mMainExecutor));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index bfb760b..248393c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -12,7 +12,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.util.SplitBounds;
+import com.android.wm.shell.shared.split.SplitBounds;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleBarLocationTest.kt
similarity index 86%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleBarLocationTest.kt
index 27e0b19..b9bf95b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleBarLocationTest.kt
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.common.bubbles
+package com.android.wm.shell.shared.bubbles
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.DEFAULT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
-import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.DEFAULT
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.LEFT
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.RIGHT
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt
index 5b22edd..641063c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles
+package com.android.wm.shell.shared.bubbles
 
 import android.os.Parcel
 import android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE
@@ -41,6 +41,7 @@
                 "com.some.package",
                 "title",
                 "Some app",
+                true,
                 true
             )
         val parcel = Parcel.obtain()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/handles/RegionSamplingHelperTest.kt
similarity index 75%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/handles/RegionSamplingHelperTest.kt
index 2a6754c..d3e291f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/handles/RegionSamplingHelperTest.kt
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.navigationbar
+package com.android.wm.shell.shared.handles
+
 
 import android.graphics.Rect
 import android.testing.TestableLooper.RunWithLooper
@@ -24,16 +25,15 @@
 import androidx.concurrent.futures.DirectExecutor
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.time.FakeSystemClock
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
 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
@@ -42,11 +42,12 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.argumentCaptor
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 @RunWithLooper
-class RegionSamplingHelperTest : SysuiTestCase() {
+class RegionSamplingHelperTest : ShellTestCase() {
 
     @Mock
     lateinit var sampledView: View
@@ -72,12 +73,16 @@
         whenever(surfaceControl.isValid).thenReturn(true)
         whenever(wrappedSurfaceControl.isValid).thenReturn(true)
         whenever(samplingCallback.isSamplingEnabled).thenReturn(true)
-        regionSamplingHelper = object : RegionSamplingHelper(sampledView, samplingCallback,
-                DirectExecutor.INSTANCE, DirectExecutor.INSTANCE, compositionListener) {
-            override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
-                return wrappedSurfaceControl
+        getInstrumentation().runOnMainSync(Runnable {
+            regionSamplingHelper = object : RegionSamplingHelper(
+                sampledView, samplingCallback,
+                DirectExecutor.INSTANCE, DirectExecutor.INSTANCE, compositionListener
+            ) {
+                override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
+                    return wrappedSurfaceControl
+                }
             }
-        }
+        })
         regionSamplingHelper.setWindowVisible(true)
     }
 
@@ -99,7 +104,7 @@
         regionSamplingHelper.setWindowHasBlurs(true)
         regionSamplingHelper.start(Rect(0, 0, 100, 100))
         verify(compositionListener, never())
-                .register(any(), anyInt(), eq(wrappedSurfaceControl), any())
+            .register(any(), anyInt(), eq(wrappedSurfaceControl), any())
     }
 
     @Test
@@ -112,35 +117,38 @@
     @Test
     fun testCompositionSamplingListener_has_nonEmptyRect() {
         // simulate race condition
-        val fakeExecutor = FakeExecutor(FakeSystemClock()) // pass in as backgroundExecutor
+        val fakeExecutor = TestShellExecutor() // pass in as backgroundExecutor
         val fakeSamplingCallback = mock(RegionSamplingHelper.SamplingCallback::class.java)
 
         whenever(fakeSamplingCallback.isSamplingEnabled).thenReturn(true)
         whenever(wrappedSurfaceControl.isValid).thenReturn(true)
-
-        regionSamplingHelper = object : RegionSamplingHelper(sampledView, fakeSamplingCallback,
-                DirectExecutor.INSTANCE, fakeExecutor, compositionListener) {
-            override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
-                return wrappedSurfaceControl
+        getInstrumentation().runOnMainSync(Runnable {
+            regionSamplingHelper = object : RegionSamplingHelper(
+                sampledView, fakeSamplingCallback,
+                DirectExecutor.INSTANCE, fakeExecutor, compositionListener
+            ) {
+                override fun wrap(stopLayerControl: SurfaceControl?): SurfaceControl {
+                    return wrappedSurfaceControl
+                }
             }
-        }
+        })
         regionSamplingHelper.setWindowVisible(true)
         regionSamplingHelper.start(Rect(0, 0, 100, 100))
 
         // make sure background task is enqueued
-        assertThat(fakeExecutor.numPending()).isEqualTo(1)
+        assertThat(fakeExecutor.getCallbacks().size).isEqualTo(1)
 
         // make sure regionSamplingHelper will have empty Rect
         whenever(fakeSamplingCallback.getSampledRegion(any())).thenReturn(Rect(0, 0, 0, 0))
         regionSamplingHelper.onLayoutChange(sampledView, 0, 0, 0, 0, 0, 0, 0, 0)
 
         // resume running of background thread
-        fakeExecutor.runAllReady()
+        fakeExecutor.flushAll()
 
         // grab Rect passed into compositionSamplingListener and make sure it's not empty
         val argumentGrabber = argumentCaptor<Rect>()
         verify(compositionListener).register(any(), anyInt(), eq(wrappedSurfaceControl),
-                argumentGrabber.capture())
-        assertThat(argumentGrabber.value.isEmpty).isFalse()
+            argumentGrabber.capture())
+        assertThat(argumentGrabber.firstValue.isEmpty).isFalse()
     }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
deleted file mode 100644
index 549bd3f..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.splitscreen;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.verify;
-
-import android.app.ActivityManager;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerTransaction;
-
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.util.Optional;
-
-/** Tests for {@link SideStage} */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class SideStageTests extends ShellTestCase {
-    @Mock private ShellTaskOrganizer mTaskOrganizer;
-    @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
-    @Mock private SyncTransactionQueue mSyncQueue;
-    @Mock private ActivityManager.RunningTaskInfo mRootTask;
-    @Mock private SurfaceControl mRootLeash;
-    @Mock private IconProvider mIconProvider;
-    @Spy private WindowContainerTransaction mWct;
-    private SurfaceSession mSurfaceSession = new SurfaceSession();
-    private SideStage mSideStage;
-
-    @Before
-    @UiThreadTest
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mRootTask = new TestRunningTaskInfoBuilder().build();
-        mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
-                mSyncQueue, mSurfaceSession, mIconProvider, Optional.empty());
-        mSideStage.onTaskAppeared(mRootTask, mRootLeash);
-    }
-
-    @Test
-    public void testAddTask() {
-        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
-
-        mSideStage.addTask(task, mWct);
-
-        verify(mWct).reparent(eq(task.token), eq(mRootTask.token), eq(true));
-    }
-
-    @Test
-    public void testRemoveTask() {
-        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
-        assertThat(mSideStage.removeTask(task.taskId, null, mWct)).isFalse();
-
-        mSideStage.mChildrenTaskInfo.put(task.taskId, task);
-        assertThat(mSideStage.removeTask(task.taskId, null, mWct)).isTrue();
-        verify(mWct).reparent(eq(task.token), isNull(), eq(false));
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index aa96c45..4de2278 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -74,7 +74,7 @@
         final SurfaceControl mRootLeash;
 
         TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
-                ShellTaskOrganizer taskOrganizer, MainStage mainStage, SideStage sideStage,
+                ShellTaskOrganizer taskOrganizer, MainStage mainStage, StageTaskListener sideStage,
                 DisplayController displayController, DisplayImeController imeController,
                 DisplayInsetsController insetsController, SplitLayout splitLayout,
                 Transitions transitions, TransactionPool transactionPool,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index abe3dcc..e167433 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -117,7 +117,7 @@
     private final TestShellExecutor mTestShellExecutor = new TestShellExecutor();
     private SplitLayout mSplitLayout;
     private MainStage mMainStage;
-    private SideStage mSideStage;
+    private StageTaskListener mSideStage;
     private StageCoordinator mStageCoordinator;
     private SplitScreenTransitions mSplitScreenTransitions;
 
@@ -137,7 +137,7 @@
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
                 mIconProvider, Optional.of(mWindowDecorViewModel)));
         mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
-        mSideStage = spy(new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
+        mSideStage = spy(new StageTaskListener(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
                 StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
                 mIconProvider, Optional.of(mWindowDecorViewModel)));
         mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 0054cb6..c9e1414 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -99,7 +99,7 @@
     @Mock
     private MainStage mMainStage;
     @Mock
-    private SideStage mSideStage;
+    private StageTaskListener mSideStage;
     @Mock
     private SplitLayout mSplitLayout;
     @Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 946a7ef..acd612e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -52,6 +53,7 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 import java.util.Optional;
 
@@ -76,6 +78,8 @@
     private IconProvider mIconProvider;
     @Mock
     private WindowDecorViewModel mWindowDecorViewModel;
+    @Spy
+    private WindowContainerTransaction mWct;
     @Captor
     private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
     private SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -177,4 +181,22 @@
         mStageTaskListener.evictAllChildren(wct);
         assertFalse(wct.isEmpty());
     }
+
+    @Test
+    public void testAddTask() {
+        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+        mStageTaskListener.addTask(task, mWct);
+
+        verify(mWct).reparent(eq(task.token), eq(mRootTask.token), eq(true));
+    }
+
+    @Test
+    public void testRemoveTask() {
+        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+        assertThat(mStageTaskListener.removeTask(task.taskId, null, mWct)).isFalse();
+
+        mStageTaskListener.mChildrenTaskInfo.put(task.taskId, task);
+        assertThat(mStageTaskListener.removeTask(task.taskId, null, mWct)).isTrue();
+        verify(mWct).reparent(eq(task.token), isNull(), eq(false));
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index 0434742..1984885 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -294,16 +294,6 @@
     }
 
     @Test
-    public void testUnsetOnBackPressedOnTaskRoot_legacyTransitions() {
-        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
-        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
-        verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
-
-        mTaskViewTaskController.onTaskVanished(mTaskInfo);
-        verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
-    }
-
-    @Test
     public void testOnNewTask_noSurface() {
         assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
         WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -443,19 +433,6 @@
     }
 
     @Test
-    public void testUnsetOnBackPressedOnTaskRoot() {
-        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
-                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
-                mLeash, wct);
-        verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
-
-        mTaskViewTaskController.prepareCloseAnimation();
-        verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
-    }
-
-    @Test
     public void testSetObscuredTouchRect() {
         mTaskView.setObscuredTouchRect(
                 new Rect(/* left= */ 0, /* top= */ 10, /* right= */ 100, /* bottom= */ 120));
@@ -713,4 +690,26 @@
         verify(mViewHandler).post(any());
         verify(mTaskView).setResizeBackgroundColor(eq(Color.BLUE));
     }
+
+    @Test
+    public void testOnAppeared_setsTrimmableTask() {
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+
+        assertThat(wct.getHierarchyOps().get(0).isTrimmableFromRecents()).isFalse();
+    }
+
+    @Test
+    public void testMoveToFullscreen_callsTaskRemovalStarted() {
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+                new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+                mLeash, wct);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        mTaskViewTaskController.moveToFullscreen();
+
+        verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index d2adae1..f51a960 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -38,6 +38,9 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.SurfaceControl;
 import android.window.TransitionInfo;
 import android.window.TransitionInfo.TransitionMode;
@@ -46,6 +49,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
@@ -57,6 +61,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -70,6 +75,8 @@
 @RunWith(AndroidJUnit4.class)
 public class HomeTransitionObserverTest extends ShellTestCase {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
     private final ShellTaskOrganizer mOrganizer = mock(ShellTaskOrganizer.class);
     private final TransactionPool mTransactionPool = mock(TransactionPool.class);
     private final Context mContext =
@@ -187,6 +194,7 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_MIGRATE_PREDICTIVE_BACK_TRANSITION)
     public void testHomeActivityWithBackGestureNotifiesHomeIsVisible() throws RemoteException {
         TransitionInfo info = mock(TransitionInfo.class);
         TransitionInfo.Change change = mock(TransitionInfo.Change.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index f7ac3e4..be0549b 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,12 +80,13 @@
 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.common.desktopmode.DesktopModeTransitionSource
+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
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
@@ -111,7 +113,7 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.verify
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.argThat
@@ -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)
@@ -594,6 +600,7 @@
 
     @Test
     fun testOnDecorSnappedLeft_snapResizes() {
+        val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
         val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
                 as ArgumentCaptor<Function0<Unit>>
         val decor = createOpenTaskDecoration(
@@ -604,8 +611,13 @@
         val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
         onLeftSnapClickListenerCaptor.value.invoke()
 
-        verify(mockDesktopTasksController)
-            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
+        verify(mockDesktopTasksController).snapToHalfScreen(
+            eq(decor.mTaskInfo),
+            taskSurfaceCaptor.capture(),
+            eq(currentBounds),
+            eq(SnapPosition.LEFT)
+        )
+        assertEquals(taskSurfaceCaptor.firstValue, decor.mTaskSurface)
     }
 
     @Test
@@ -626,6 +638,7 @@
     @Test
     @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
     fun testOnSnapResizeLeft_nonResizable_decorSnappedLeft() {
+        val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
         val onLeftSnapClickListenerCaptor = forClass(Function0::class.java)
                 as ArgumentCaptor<Function0<Unit>>
         val decor = createOpenTaskDecoration(
@@ -636,8 +649,13 @@
         val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
         onLeftSnapClickListenerCaptor.value.invoke()
 
-        verify(mockDesktopTasksController)
-            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
+        verify(mockDesktopTasksController).snapToHalfScreen(
+            eq(decor.mTaskInfo),
+            taskSurfaceCaptor.capture(),
+            eq(currentBounds),
+            eq(SnapPosition.LEFT)
+        )
+        assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
     }
 
     @Test
@@ -654,12 +672,13 @@
         onLeftSnapClickListenerCaptor.value.invoke()
 
         verify(mockDesktopTasksController, never())
-            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
+            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT))
         verify(mockToast).show()
     }
 
     @Test
     fun testOnDecorSnappedRight_snapResizes() {
+        val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
         val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
                 as ArgumentCaptor<Function0<Unit>>
         val decor = createOpenTaskDecoration(
@@ -670,8 +689,13 @@
         val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
         onRightSnapClickListenerCaptor.value.invoke()
 
-        verify(mockDesktopTasksController)
-            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
+        verify(mockDesktopTasksController).snapToHalfScreen(
+            eq(decor.mTaskInfo),
+            taskSurfaceCaptor.capture(),
+            eq(currentBounds),
+            eq(SnapPosition.RIGHT)
+        )
+        assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
     }
 
     @Test
@@ -692,6 +716,7 @@
     @Test
     @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
     fun testOnSnapResizeRight_nonResizable_decorSnappedRight() {
+        val taskSurfaceCaptor = argumentCaptor<SurfaceControl>()
         val onRightSnapClickListenerCaptor = forClass(Function0::class.java)
                 as ArgumentCaptor<Function0<Unit>>
         val decor = createOpenTaskDecoration(
@@ -702,8 +727,13 @@
         val currentBounds = decor.mTaskInfo.configuration.windowConfiguration.bounds
         onRightSnapClickListenerCaptor.value.invoke()
 
-        verify(mockDesktopTasksController)
-            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
+        verify(mockDesktopTasksController).snapToHalfScreen(
+            eq(decor.mTaskInfo),
+            taskSurfaceCaptor.capture(),
+            eq(currentBounds),
+            eq(SnapPosition.RIGHT)
+        )
+        assertEquals(decor.mTaskSurface, taskSurfaceCaptor.firstValue)
     }
 
     @Test
@@ -720,7 +750,7 @@
         onRightSnapClickListenerCaptor.value.invoke()
 
         verify(mockDesktopTasksController, never())
-            .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
+            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT))
         verify(mockToast).show()
     }
 
@@ -888,12 +918,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
@@ -1027,6 +1057,7 @@
 
     private fun createOpenTaskDecoration(
         @WindowingMode windowingMode: Int,
+        taskSurface: SurfaceControl = SurfaceControl(),
         onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
             forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
         onLeftSnapClickListenerCaptor: ArgumentCaptor<Function0<Unit>> =
@@ -1045,7 +1076,7 @@
             forClass(View.OnClickListener::class.java) as ArgumentCaptor<View.OnClickListener>
     ): DesktopModeWindowDecoration {
         val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode))
-        onTaskOpening(decor.mTaskInfo)
+        onTaskOpening(decor.mTaskInfo, taskSurface)
         verify(decor).setOnMaximizeOrRestoreClickListener(onMaxOrRestoreListenerCaptor.capture())
         verify(decor).setOnLeftSnapClickListener(onLeftSnapClickListenerCaptor.capture())
         verify(decor).setOnRightSnapClickListener(onRightSnapClickListenerCaptor.capture())
@@ -1104,6 +1135,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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 596adfb..258c860 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -173,9 +173,12 @@
     @Mock
     private AppToWebGenericLinksParser mMockGenericLinksParser;
     @Mock
+    private WindowManager mMockWindowManager;
+    @Mock
     private HandleMenu mMockHandleMenu;
     @Mock
     private HandleMenuFactory mMockHandleMenuFactory;
+    @Mock
     private MultiInstanceHelper mMockMultiInstanceHelper;
     @Captor
     private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@@ -220,9 +223,10 @@
         final Display defaultDisplay = mock(Display.class);
         doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
         doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
-        when(mMockHandleMenuFactory.create(any(), anyInt(), any(), any(),
+        when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(),
                 any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt()))
                 .thenReturn(mMockHandleMenu);
+        when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
     }
 
     @After
@@ -385,6 +389,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void updateRelayoutParams_fullscreen_inputChannelNotNeeded() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -401,6 +406,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void updateRelayoutParams_multiwindow_inputChannelNotNeeded() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -491,7 +497,7 @@
                 .isTrue();
     }
 
-    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
+    @Test
     public void relayout_fullscreenTask_appliesTransactionImmediately() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -518,7 +524,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -530,7 +535,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_fullscreenTask_postsViewHostCreation() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -559,7 +563,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void relayout_removesExistingHandlerCallback() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -574,7 +577,6 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     public void close_removesExistingHandlerCallback() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
         final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo));
@@ -826,7 +828,7 @@
     }
 
     private void verifyHandleMenuCreated(@Nullable Uri uri) {
-        verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
+        verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
                 any(), anyBoolean(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt());
     }
 
@@ -894,8 +896,9 @@
                 mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
                 mMockGenericLinksParser, SurfaceControl.Builder::new, mMockTransactionSupplier,
                 WindowContainerTransaction::new, SurfaceControl::new,
+                new WindowManagerWrapper(mMockWindowManager),
                 mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory,
-                        mMockMultiInstanceHelper);
+                mMockMultiInstanceHelper);
         windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
                 mMockTouchEventListener, mMockTouchEventListener);
         windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 627dfe7..100abbb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -33,6 +33,7 @@
 import android.view.SurfaceControl
 import android.view.SurfaceControlViewHost
 import android.view.View
+import android.view.WindowManager
 import androidx.core.graphics.toPointF
 import androidx.test.filters.SmallTest
 import com.android.window.flags.Flags
@@ -77,6 +78,8 @@
     @Mock
     private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
     @Mock
+    private lateinit var mockWindowManager: WindowManager
+    @Mock
     private lateinit var onClickListener: View.OnClickListener
     @Mock
     private lateinit var onTouchListener: View.OnTouchListener
@@ -230,8 +233,9 @@
             }
             else -> error("Invalid windowing mode")
         }
-        val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId, appIcon, appName,
-            splitScreenController, shouldShowWindowingPill = true,
+        val handleMenu = HandleMenu(mockDesktopWindowDecoration,
+            WindowManagerWrapper(mockWindowManager),
+            layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true,
             shouldShowNewWindowButton = true,
             null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
             captionX = captionX
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
index 28b4eb6..0f52ed7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.R
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.WindowManagerWrapper
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -70,6 +71,7 @@
                 WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
         viewContainer = AdditionalSystemViewContainer(
             mockContext,
+            WindowManagerWrapper(mockWindowManager),
             TASK_ID,
             X,
             Y,
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 43a70c1..5a4cff0 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -309,9 +309,8 @@
         return NULL;
     }
 
-    // We succeeded, so relinquish control of dataMap
     pAsset->mAccessMode = mode;
-    return std::move(pAsset);
+    return pAsset;
 }
 
 /*
@@ -328,9 +327,8 @@
       return NULL;
   }
 
-  // We succeeded, so relinquish control of dataMap
   pAsset->mAccessMode = mode;
-  return std::move(pAsset);
+  return pAsset;
 }
 
 /*
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 31c9db7..3a3bfb47 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -106,7 +106,7 @@
 
 private:
     sk_sp<SkColorFilter> createInstance() override {
-        return SkColorFilters::Matrix(mMatrix.data());
+        return SkColorFilters::Matrix(mMatrix.data(), SkColorFilters::Clamp::kNo);
     }
 
 private:
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index ac75c07..c1c30f5 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -49,6 +49,15 @@
 #endif  // __ANDROID__
 }
 
+inline bool typeface_redesign() {
+#ifdef __ANDROID__
+    static bool flag = com_android_text_flags_typeface_redesign();
+    return flag;
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
 }  // namespace text_feature
 
 }  // namespace android
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index f8574ee..1510ce1 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -27,6 +27,8 @@
 #include <cutils/compiler.h>
 #include <log/log.h>
 #include <minikin/Layout.h>
+
+#include "FeatureFlags.h"
 #include "MinikinSkia.h"
 #include "Paint.h"
 #include "Typeface.h"
@@ -71,27 +73,42 @@
     static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
         float saveSkewX = paint->getSkFont().getSkewX();
         bool savefakeBold = paint->getSkFont().isEmbolden();
-        const minikin::MinikinFont* curFont = nullptr;
-        size_t start = 0;
-        size_t nGlyphs = layout.nGlyphs();
-        for (size_t i = 0; i < nGlyphs; i++) {
-            const minikin::MinikinFont* nextFont = layout.typeface(i).get();
-            if (i > 0 && nextFont != curFont) {
+        if (text_feature::typeface_redesign()) {
+            for (uint32_t runIdx = 0; runIdx < layout.getFontRunCount(); ++runIdx) {
+                uint32_t start = layout.getFontRunStart(runIdx);
+                uint32_t end = layout.getFontRunEnd(runIdx);
+                const minikin::FakedFont& fakedFont = layout.getFontRunFont(runIdx);
+
+                std::shared_ptr<minikin::MinikinFont> font = fakedFont.typeface();
                 SkFont* skfont = &paint->getSkFont();
-                MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
-                f(start, i);
+                MinikinFontSkia::populateSkFont(skfont, font.get(), fakedFont.fakery);
+                f(start, end);
                 skfont->setSkewX(saveSkewX);
                 skfont->setEmbolden(savefakeBold);
-                start = i;
             }
-            curFont = nextFont;
-        }
-        if (nGlyphs > start) {
-            SkFont* skfont = &paint->getSkFont();
-            MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
-            f(start, nGlyphs);
-            skfont->setSkewX(saveSkewX);
-            skfont->setEmbolden(savefakeBold);
+        } else {
+            const minikin::MinikinFont* curFont = nullptr;
+            size_t start = 0;
+            size_t nGlyphs = layout.nGlyphs();
+            for (size_t i = 0; i < nGlyphs; i++) {
+                const minikin::MinikinFont* nextFont = layout.typeface(i).get();
+                if (i > 0 && nextFont != curFont) {
+                    SkFont* skfont = &paint->getSkFont();
+                    MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
+                    f(start, i);
+                    skfont->setSkewX(saveSkewX);
+                    skfont->setEmbolden(savefakeBold);
+                    start = i;
+                }
+                curFont = nextFont;
+            }
+            if (nGlyphs > start) {
+                SkFont* skfont = &paint->getSkFont();
+                MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
+                f(start, nGlyphs);
+                skfont->setSkewX(saveSkewX);
+                skfont->setEmbolden(savefakeBold);
+            }
         }
     }
 };
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8bb11ba..dfda25d 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -761,8 +761,8 @@
         if (mExpectSurfaceStats) {
             reportMetricsWithPresentTime();
             {  // acquire lock
-                std::lock_guard lock(mLast4FrameMetricsInfosMutex);
-                FrameMetricsInfo& next = mLast4FrameMetricsInfos.next();
+                std::lock_guard lock(mLastFrameMetricsInfosMutex);
+                FrameMetricsInfo& next = mLastFrameMetricsInfos.next();
                 next.frameInfo = mCurrentFrameInfo;
                 next.frameNumber = frameCompleteNr;
                 next.surfaceId = mSurfaceControlGenerationId;
@@ -816,12 +816,12 @@
     int32_t surfaceControlId;
 
     {  // acquire lock
-        std::scoped_lock lock(mLast4FrameMetricsInfosMutex);
-        if (mLast4FrameMetricsInfos.size() != mLast4FrameMetricsInfos.capacity()) {
+        std::scoped_lock lock(mLastFrameMetricsInfosMutex);
+        if (mLastFrameMetricsInfos.size() != mLastFrameMetricsInfos.capacity()) {
             // Not enough frames yet
             return;
         }
-        auto frameMetricsInfo = mLast4FrameMetricsInfos.front();
+        auto frameMetricsInfo = mLastFrameMetricsInfos.front();
         forthBehind = frameMetricsInfo.frameInfo;
         frameNumber = frameMetricsInfo.frameNumber;
         surfaceControlId = frameMetricsInfo.surfaceId;
@@ -869,12 +869,12 @@
     }
 }
 
-FrameInfo* CanvasContext::getFrameInfoFromLast4(uint64_t frameNumber, uint32_t surfaceControlId) {
-    std::scoped_lock lock(mLast4FrameMetricsInfosMutex);
-    for (size_t i = 0; i < mLast4FrameMetricsInfos.size(); i++) {
-        if (mLast4FrameMetricsInfos[i].frameNumber == frameNumber &&
-            mLast4FrameMetricsInfos[i].surfaceId == surfaceControlId) {
-            return mLast4FrameMetricsInfos[i].frameInfo;
+FrameInfo* CanvasContext::getFrameInfoFromLastFew(uint64_t frameNumber, uint32_t surfaceControlId) {
+    std::scoped_lock lock(mLastFrameMetricsInfosMutex);
+    for (size_t i = 0; i < mLastFrameMetricsInfos.size(); i++) {
+        if (mLastFrameMetricsInfos[i].frameNumber == frameNumber &&
+            mLastFrameMetricsInfos[i].surfaceId == surfaceControlId) {
+            return mLastFrameMetricsInfos[i].frameInfo;
         }
     }
 
@@ -894,7 +894,7 @@
     }
     uint64_t frameNumber = functions.getFrameNumberFunc(stats);
 
-    FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
+    FrameInfo* frameInfo = instance->getFrameInfoFromLastFew(frameNumber, surfaceControlId);
 
     if (frameInfo != nullptr) {
         std::scoped_lock lock(instance->mFrameInfoMutex);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index e2e3fa3..cb37538 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -260,7 +260,7 @@
     void finishFrame(FrameInfo* frameInfo);
 
     /**
-     * Invoke 'reportFrameMetrics' on the last frame stored in 'mLast4FrameInfos'.
+     * Invoke 'reportFrameMetrics' on the last frame stored in 'mLastFrameInfos'.
      * Populate the 'presentTime' field before calling.
      */
     void reportMetricsWithPresentTime();
@@ -271,7 +271,7 @@
         int32_t surfaceId;
     };
 
-    FrameInfo* getFrameInfoFromLast4(uint64_t frameNumber, uint32_t surfaceControlId);
+    FrameInfo* getFrameInfoFromLastFew(uint64_t frameNumber, uint32_t surfaceControlId);
 
     Frame getFrame();
 
@@ -336,9 +336,9 @@
 
     // List of data of frames that are awaiting GPU completion reporting. Used to compute frame
     // metrics and determine whether or not to report the metrics.
-    RingBuffer<FrameMetricsInfo, 4> mLast4FrameMetricsInfos
-            GUARDED_BY(mLast4FrameMetricsInfosMutex);
-    std::mutex mLast4FrameMetricsInfosMutex;
+    RingBuffer<FrameMetricsInfo, 6> mLastFrameMetricsInfos
+            GUARDED_BY(mLastFrameMetricsInfosMutex);
+    std::mutex mLastFrameMetricsInfosMutex;
 
     std::string mName;
     JankTracker mJankTracker;
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index fd596d9..e427c97 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -16,6 +16,7 @@
 
 #include "tests/common/TestContext.h"
 
+#include <com_android_graphics_libgui_flags.h>
 #include <cutils/trace.h>
 
 namespace android {
@@ -101,6 +102,14 @@
 }
 
 void TestContext::createOffscreenSurface() {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    mConsumer = new BufferItemConsumer(GRALLOC_USAGE_HW_COMPOSER, 4);
+    const ui::Size& resolution = getActiveDisplayResolution();
+    mConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
+    mSurface = mConsumer->getSurface();
+    mSurface->setMaxDequeuedBufferCount(3);
+    mSurface->setAsyncMode(true);
+#else
     sp<IGraphicBufferProducer> producer;
     sp<IGraphicBufferConsumer> consumer;
     BufferQueue::createBufferQueue(&producer, &consumer);
@@ -110,6 +119,7 @@
     const ui::Size& resolution = getActiveDisplayResolution();
     mConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
     mSurface = new Surface(producer);
+#endif  // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 }
 
 void TestContext::waitForVsync() {
@@ -144,4 +154,4 @@
 
 }  // namespace test
 }  // namespace uirenderer
-}  // namespace android
+}  // namespace android
\ No newline at end of file
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index eecc741..1afef75 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -25,6 +25,9 @@
 #include <input/Input.h>
 #include <log/log.h>
 
+#define INDENT "  "
+#define INDENT2 "    "
+
 namespace {
 // Time to spend fading out the pointer completely.
 const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
@@ -449,6 +452,24 @@
     return mLocked.resourcesLoaded;
 }
 
+std::string MouseCursorController::dump() const {
+    std::string dump = INDENT "MouseCursorController:\n";
+    std::scoped_lock lock(mLock);
+    dump += StringPrintf(INDENT2 "viewport: %s\n", mLocked.viewport.toString().c_str());
+    dump += StringPrintf(INDENT2 "stylusHoverMode: %s\n",
+                         mLocked.stylusHoverMode ? "true" : "false");
+    dump += StringPrintf(INDENT2 "pointerFadeDirection: %d\n", mLocked.pointerFadeDirection);
+    dump += StringPrintf(INDENT2 "updatePointerIcon: %s\n",
+                         mLocked.updatePointerIcon ? "true" : "false");
+    dump += StringPrintf(INDENT2 "resourcesLoaded: %s\n",
+                         mLocked.resourcesLoaded ? "true" : "false");
+    dump += StringPrintf(INDENT2 "requestedPointerType: %d\n", mLocked.requestedPointerType);
+    dump += StringPrintf(INDENT2 "resolvedPointerType: %d\n", mLocked.resolvedPointerType);
+    dump += StringPrintf(INDENT2 "skipScreenshot: %s\n", mLocked.skipScreenshot ? "true" : "false");
+    dump += StringPrintf(INDENT2 "animating: %s\n", mLocked.animating ? "true" : "false");
+    return dump;
+}
+
 bool MouseCursorController::doAnimations(nsecs_t timestamp) {
     std::scoped_lock lock(mLock);
     bool keepFading = doFadingAnimationLocked(timestamp);
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 78f6413..8600341 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -67,6 +67,8 @@
 
     bool resourcesLoaded();
 
+    std::string dump() const;
+
 private:
     mutable std::mutex mLock;
 
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 11b27a2..5ae967b 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -25,6 +25,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
 #include <ftl/enum.h>
+#include <input/PrintTools.h>
 
 #include <mutex>
 
@@ -353,6 +354,8 @@
     for (const auto& [_, spotController] : mLocked.spotControllers) {
         spotController.dump(dump, INDENT3);
     }
+    dump += INDENT2 "Cursor Controller:\n";
+    dump += addLinePrefix(mCursorController.dump(), INDENT3);
     return dump;
 }
 
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index f6e76a2..cf3f740 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -437,7 +437,7 @@
   }
 
   public class LocationManager {
-    method @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void addProviderRequestChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.provider.ProviderRequest.ChangedListener);
+    method @Deprecated @FlaggedApi("android.location.flags.deprecate_provider_request_apis") @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void addProviderRequestChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.provider.ProviderRequest.ChangedListener);
     method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void flushGnssBatch();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void getCurrentLocation(@NonNull android.location.LocationRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.location.Location>);
     method @Nullable public String getExtraLocationControllerPackage();
@@ -452,7 +452,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@Nullable String, @NonNull String, @Nullable String);
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.UPDATE_APP_OPS_STATS}) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback);
-    method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void removeProviderRequestChangedListener(@NonNull android.location.provider.ProviderRequest.ChangedListener);
+    method @Deprecated @FlaggedApi("android.location.flags.deprecate_provider_request_apis") @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void removeProviderRequestChangedListener(@NonNull android.location.provider.ProviderRequest.ChangedListener);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index d921730..6342489 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -28,6 +28,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
@@ -47,6 +48,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.location.flags.Flags;
 import android.location.provider.IProviderRequestListener;
 import android.location.provider.ProviderProperties;
 import android.location.provider.ProviderRequest;
@@ -2957,8 +2959,13 @@
      *
      * @param executor the executor that the callback runs on
      * @param listener the listener to register
+     *
+     * @deprecated Do not use - this API was intended for testing only, and may not return any
+     * results in the future.
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_DEPRECATE_PROVIDER_REQUEST_APIS)
+    @Deprecated
     @SystemApi
     @RequiresPermission(allOf = {Manifest.permission.LOCATION_HARDWARE,
             Manifest.permission.INTERACT_ACROSS_USERS})
@@ -2973,8 +2980,13 @@
      * Removes a {@link ProviderRequest.ChangedListener} that has been added.
      *
      * @param listener the listener to remove.
+     *
+     * @deprecated Do not use - this API was intended for testing only, and may not return any
+     * results in the future.
      * @hide
      */
+    @FlaggedApi(Flags.FLAG_DEPRECATE_PROVIDER_REQUEST_APIS)
+    @Deprecated
     @SystemApi
     @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
     public void removeProviderRequestChangedListener(
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 0edaaef..3c387d3 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -2,6 +2,13 @@
 container: "system"
 
 flag {
+    name: "deprecate_provider_request_apis"
+    namespace: "location"
+    description: "Deprecates LocationManager ProviderChanged APIs"
+    bug: "361811782"
+}
+
+flag {
     name: "keep_gnss_stationary_throttling"
     namespace: "location"
     description: "Keeps stationary throttling for the GNSS provider even if the disable_stationary_throttling flag is 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/AudioSystem.java b/media/java/android/media/AudioSystem.java
index a255f73..ebdfd3e 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2655,7 +2655,16 @@
     /**
      * Register a native listener for system property sysprop
      * @param callback the listener which fires when the property changes
+     * @return a native handle for use in subsequent methods
      * @hide
      */
-    public static native void listenForSystemPropertyChange(String sysprop, Runnable callback);
+    public static native long listenForSystemPropertyChange(String sysprop, Runnable callback);
+
+    /**
+     * Trigger a sysprop listener update, if the property has been updated: synchronously validating
+     * there are no pending sysprop changes.
+     * @param handle the handle returned by {@link listenForSystemPropertyChange}
+     * @hide
+     */
+    public static native void triggerSystemPropertyUpdate(long handle);
 }
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/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 1930c3d..b84990b 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -288,8 +288,7 @@
 
     /**
      * Returns a proxy MediaRouter2 instance that allows you to control the routing of an app
-     * specified by {@code clientPackageName}. Returns {@code null} if the specified package name
-     * does not exist.
+     * specified by {@code clientPackageName}.
      *
      * <p>Proxy MediaRouter2 instances operate differently than regular MediaRouter2 instances:
      *
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 0829a90e..93bca21 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -26,11 +26,11 @@
 }
 
 flag {
-    name: "tis_always_bound_permission"
+    name: "tif_unbind_inactive_tis"
     is_exported: true
     namespace: "media_tv"
-    description: "Introduce ALWAYS_BOUND_TV_INPUT for TIS."
-    bug: "332201346"
+    description: "Unbind hardware TIS when not needed"
+    bug: "279189366"
 }
 
 flag {
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/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 371e3d2..019b1e0 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -17,35 +17,31 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "ImageReader_JNI"
 #define ATRACE_TAG ATRACE_TAG_CAMERA
-#include "android_media_Utils.h"
+#include <android/hardware_buffer_jni.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_graphics_GraphicBuffer.h>
+#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <android_runtime/android_view_Surface.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <cutils/atomic.h>
-#include <utils/Log.h>
-#include <utils/misc.h>
+#include <grallocusage/GrallocUsageConversion.h>
+#include <gui/BufferItemConsumer.h>
+#include <gui/Surface.h>
+#include <inttypes.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <private/android/AHardwareBufferHelpers.h>
+#include <stdint.h>
+#include <ui/Rect.h>
 #include <utils/List.h>
-#include <utils/Trace.h>
+#include <utils/Log.h>
 #include <utils/String8.h>
+#include <utils/Trace.h>
+#include <utils/misc.h>
 
 #include <cstdio>
 
-#include <gui/BufferItemConsumer.h>
-#include <gui/Surface.h>
-
-#include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/android_view_Surface.h>
-#include <android_runtime/android_graphics_GraphicBuffer.h>
-#include <android_runtime/android_hardware_HardwareBuffer.h>
-#include <grallocusage/GrallocUsageConversion.h>
-
-#include <private/android/AHardwareBufferHelpers.h>
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <stdint.h>
-#include <inttypes.h>
-#include <android/hardware_buffer_jni.h>
-
-#include <ui/Rect.h>
+#include "android_media_Utils.h"
 
 #define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID       "mNativeContext"
 #define ANDROID_MEDIA_SURFACEIMAGE_BUFFER_JNI_ID   "mNativeBuffer"
@@ -393,18 +389,25 @@
     }
     sp<JNIImageReaderContext> ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages));
 
-    sp<IGraphicBufferProducer> gbProducer;
-    sp<IGraphicBufferConsumer> gbConsumer;
-    BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
-    sp<BufferItemConsumer> bufferConsumer;
     String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
             width, height, nativeHalFormat, maxImages, getpid(),
             createProcessUniqueId());
     uint64_t consumerUsage =
             android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    sp<BufferItemConsumer> bufferConsumer = new BufferItemConsumer(consumerUsage, maxImages,
+                                                                   /*controlledByApp*/ true);
+    sp<IGraphicBufferProducer> gbProducer =
+            bufferConsumer->getSurface()->getIGraphicBufferProducer();
+#else
+    sp<IGraphicBufferProducer> gbProducer;
+    sp<IGraphicBufferConsumer> gbConsumer;
+    BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
+    sp<BufferItemConsumer> bufferConsumer;
     bufferConsumer = new BufferItemConsumer(gbConsumer, consumerUsage, maxImages,
             /*controlledByApp*/true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     if (bufferConsumer == nullptr) {
         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
                 "Failed to allocate native buffer consumer for hal format 0x%x and usage 0x%x",
@@ -413,7 +416,11 @@
     }
 
     if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        bufferConsumer->setConsumerIsProtected(true);
+#else
         gbConsumer->setConsumerIsProtected(true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     }
 
     ctx->setBufferConsumer(bufferConsumer);
diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp
index 1bb82f8..4637ccd 100644
--- a/media/mca/filterfw/native/core/gl_env.cpp
+++ b/media/mca/filterfw/native/core/gl_env.cpp
@@ -15,21 +15,23 @@
  */
 // #define LOG_NDEBUG 0
 
-#include "base/logging.h"
-#include "base/utilities.h"
 #include "core/gl_env.h"
-#include "core/shader_program.h"
-#include "core/vertex_frame.h"
-#include "system/window.h"
+
+#include <EGL/eglext.h>
+#include <com_android_graphics_libgui_flags.h>
+#include <gui/BufferQueue.h>
+#include <gui/GLConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/Surface.h>
 
 #include <map>
 #include <string>
-#include <EGL/eglext.h>
 
-#include <gui/BufferQueue.h>
-#include <gui/Surface.h>
-#include <gui/GLConsumer.h>
-#include <gui/IGraphicBufferProducer.h>
+#include "base/logging.h"
+#include "base/utilities.h"
+#include "core/shader_program.h"
+#include "core/vertex_frame.h"
+#include "system/window.h"
 
 namespace android {
 namespace filterfw {
@@ -165,12 +167,18 @@
   }
 
   // Create dummy surface using a GLConsumer
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+  surfaceTexture_ = new GLConsumer(0, GLConsumer::TEXTURE_EXTERNAL, /*useFenceSync=*/true,
+                                   /*isControlledByApp=*/false);
+  window_ = surfaceTexture_->getSurface();
+#else
   sp<IGraphicBufferProducer> producer;
   sp<IGraphicBufferConsumer> consumer;
   BufferQueue::createBufferQueue(&producer, &consumer);
   surfaceTexture_ = new GLConsumer(consumer, 0, GLConsumer::TEXTURE_EXTERNAL,
           true, false);
   window_ = new Surface(producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
   surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL);
   if (CheckEGLError("eglCreateWindowSurface")) return false;
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
index a991a71f..7acb8c7 100644
--- a/media/native/midi/Android.bp
+++ b/media/native/midi/Android.bp
@@ -74,8 +74,4 @@
     symbol_file: "libamidi.map.txt",
 
     first_version: "29",
-    export_header_libs: [
-        "amidi",
-    ],
-
 }
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 3dc2a0a..43b1a35 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -24,3 +24,11 @@
     resource_dirs: ["res"],
     test_suites: ["device-tests"],
 }
+
+test_module_config {
+    name: "audiopolicytest_audiopolicytest_audiopolicydeathtest_Presubmit",
+    base: "audiopolicytest",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.audiopolicytest.AudioPolicyDeathTest"],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+}
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/native/android/Android.bp b/native/android/Android.bp
index c4c4102..3eb99c3 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -27,9 +27,6 @@
     symbol_file: "libandroid.map.txt",
     first_version: "9",
     unversioned_until: "current",
-    export_header_libs: [
-        "libandroid_headers",
-    ],
 }
 
 cc_defaults {
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..0f97b2c 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -73,6 +73,7 @@
     method public void onApplyRouting(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onBootFinished(int);
     method public void onBootStarted();
+    method public void onCardEmulationActivated(boolean);
     method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onDisableFinished(int);
     method public void onDisableStarted();
@@ -81,6 +82,8 @@
     method public void onEnableStarted();
     method public void onHceEventReceived(int);
     method public void onNdefRead(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onRfDiscoveryStarted(boolean);
+    method public void onRfFieldActivated(boolean);
     method public void onRoutingChanged();
     method public void onStateUpdated(int);
     method public void onTagConnected(boolean, @NonNull android.nfc.Tag);
@@ -94,7 +97,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/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 6c0f933..e2ec952 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -62,7 +62,7 @@
 
     void dispatch(in Tag tag);
 
-    void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras);
+    void setReaderMode (IBinder b, IAppCallback callback, int flags, in Bundle extras, String pkg);
 
     void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, in int[] techList);
     void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
@@ -100,7 +100,7 @@
     void unregisterWlcStateListener(in INfcWlcStateListener listener);
     WlcListenerDeviceInfo getWlcListenerDeviceInfo();
 
-    void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
+    void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags, String pkg);
 
     void notifyPollingLoop(in PollingFrame frame);
     void notifyHceDeactivated();
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index c19a44b..b65c837 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -37,4 +37,7 @@
    void onTagDispatch(in ResultReceiver isSkipped);
    void onRoutingChanged();
    void onHceEventReceived(int action);
+   void onCardEmulationActivated(boolean isActivated);
+   void onRfFieldActivated(boolean isActivated);
+   void onRfDiscoveryStarted(boolean isDiscoveryStarted);
 }
diff --git a/nfc/java/android/nfc/NfcActivityManager.java b/nfc/java/android/nfc/NfcActivityManager.java
index 0eb846d..909eca7 100644
--- a/nfc/java/android/nfc/NfcActivityManager.java
+++ b/nfc/java/android/nfc/NfcActivityManager.java
@@ -236,7 +236,8 @@
 
     public void setReaderMode(Binder token, int flags, Bundle extras) {
         if (DBG) Log.d(TAG, "Setting reader mode");
-        NfcAdapter.callService(() -> NfcAdapter.sService.setReaderMode(token, this, flags, extras));
+        NfcAdapter.callService(() -> NfcAdapter.sService.setReaderMode(
+                token, this, flags, extras, mAdapter.getContext().getPackageName()));
     }
 
     /**
@@ -395,7 +396,8 @@
 
     private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) {
         NfcAdapter.callService(
-            () -> NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech));
+                () -> NfcAdapter.sService.updateDiscoveryTechnology(
+                        token, pollTech, listenTech, mAdapter.getContext().getPackageName()));
     }
 
 }
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 525e2c5..22ae612 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1731,7 +1731,8 @@
         }
         Binder token = new Binder();
         int flags = enable ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
-        callService(() -> sService.setReaderMode(token, null, flags, null));
+        callService(() -> sService.setReaderMode(
+                token, null, flags, null, mContext.getPackageName()));
     }
 
     /**
@@ -1804,7 +1805,8 @@
                 || (listenTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH)) {
             Binder token = new Binder();
             callService( () ->
-                sService.updateDiscoveryTechnology(token, pollTechnology, listenTechnology));
+                    sService.updateDiscoveryTechnology(
+                            token, pollTechnology, listenTechnology, mContext.getPackageName()));
         } else {
             mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
         }
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 6c02edd..632f693 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -32,7 +32,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
@@ -40,6 +42,7 @@
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -58,10 +61,13 @@
     private static final int OEM_EXTENSION_RESPONSE_THRESHOLD_MS = 2000;
     private final NfcAdapter mAdapter;
     private final NfcOemExtensionCallback mOemNfcExtensionCallback;
+    private boolean mIsRegistered = false;
+    private final Map<Callback, Executor> mCallbackMap = new HashMap<>();
     private final Context mContext;
-    private Executor mExecutor = null;
-    private Callback mCallback = null;
     private final Object mLock = new Object();
+    private boolean mCardEmulationActivated = false;
+    private boolean mRfFieldActivated = false;
+    private boolean mRfDiscoveryStarted = false;
 
     /**
      * Event that Host Card Emulation is activated.
@@ -215,6 +221,32 @@
          * @param action Flag indicating actions to activate, start and stop cpu boost.
          */
         void onHceEventReceived(@HostCardEmulationAction int action);
+
+        /**
+        * Notifies NFC is activated in listen mode.
+        * NFC Forum NCI-2.3 ch.5.2.6 specification
+        *
+        * <p>NFCC is ready to communicate with a Card reader
+        *
+        * @param isActivated true, if card emulation activated, else de-activated.
+        */
+        void onCardEmulationActivated(boolean isActivated);
+
+        /**
+        * Notifies the Remote NFC Endpoint RF Field is activated.
+        * NFC Forum NCI-2.3 ch.5.3 specification
+        *
+        * @param isActivated true, if RF Field is ON, else RF Field is OFF.
+        */
+        void onRfFieldActivated(boolean isActivated);
+
+        /**
+        * Notifies the NFC RF discovery is started or in the IDLE state.
+        * NFC Forum NCI-2.3 ch.5.2 specification
+        *
+        * @param isDiscoveryStarted true, if RF discovery started, else RF state is Idle.
+        */
+        void onRfDiscoveryStarted(boolean isDiscoveryStarted);
     }
 
 
@@ -229,7 +261,12 @@
 
     /**
      * Register an {@link Callback} to listen for NFC oem extension callbacks
+     * Multiple clients can register and callbacks will be invoked asynchronously.
+     *
      * <p>The provided callback will be invoked by the given {@link Executor}.
+     * As part of {@link #registerCallback(Executor, Callback)} the
+     * {@link Callback} will be invoked with current NFC state
+     * before the {@link #registerCallback(Executor, Callback)} function completes.
      *
      * @param executor an {@link Executor} to execute given callback
      * @param callback oem implementation of {@link Callback}
@@ -239,15 +276,35 @@
     public void registerCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull Callback callback) {
         synchronized (mLock) {
-            if (mCallback != null) {
+            if (executor == null || callback == null) {
+                Log.e(TAG, "Executor and Callback must not be null!");
+                throw new IllegalArgumentException();
+            }
+
+            if (mCallbackMap.containsKey(callback)) {
                 Log.e(TAG, "Callback already registered. Unregister existing callback before"
                         + "registering");
                 throw new IllegalArgumentException();
             }
-            NfcAdapter.callService(() -> {
-                NfcAdapter.sService.registerOemExtensionCallback(mOemNfcExtensionCallback);
-                mCallback = callback;
-                mExecutor = executor;
+            mCallbackMap.put(callback, executor);
+            if (!mIsRegistered) {
+                NfcAdapter.callService(() -> {
+                    NfcAdapter.sService.registerOemExtensionCallback(mOemNfcExtensionCallback);
+                    mIsRegistered = true;
+                });
+            } else {
+                updateNfCState(callback, executor);
+            }
+        }
+    }
+
+    private void updateNfCState(Callback callback, Executor executor) {
+        if (callback != null) {
+            Log.i(TAG, "updateNfCState");
+            executor.execute(() -> {
+                callback.onCardEmulationActivated(mCardEmulationActivated);
+                callback.onRfFieldActivated(mRfFieldActivated);
+                callback.onRfDiscoveryStarted(mRfDiscoveryStarted);
             });
         }
     }
@@ -266,15 +323,19 @@
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public void unregisterCallback(@NonNull Callback callback) {
         synchronized (mLock) {
-            if (mCallback == null || mCallback != callback) {
+            if (!mCallbackMap.containsKey(callback) || !mIsRegistered) {
                 Log.e(TAG, "Callback not registered");
                 throw new IllegalArgumentException();
             }
-            NfcAdapter.callService(() -> {
-                NfcAdapter.sService.unregisterOemExtensionCallback(mOemNfcExtensionCallback);
-                mCallback = null;
-                mExecutor = null;
-            });
+            if (mCallbackMap.size() == 1) {
+                NfcAdapter.callService(() -> {
+                    NfcAdapter.sService.unregisterOemExtensionCallback(mOemNfcExtensionCallback);
+                    mIsRegistered = false;
+                    mCallbackMap.remove(callback);
+                });
+            } else {
+                mCallbackMap.remove(callback);
+            }
         }
     }
 
@@ -322,90 +383,133 @@
     }
 
     private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
+
         @Override
         public void onTagConnected(boolean connected, Tag tag) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoid2ArgCallback(connected, tag, cb::onTagConnected, ex));
+        }
+
+        @Override
+        public void onCardEmulationActivated(boolean isActivated) throws RemoteException {
+            mCardEmulationActivated = isActivated;
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(isActivated, cb::onCardEmulationActivated, ex));
+        }
+
+        @Override
+        public void onRfFieldActivated(boolean isActivated) throws RemoteException {
+            mRfFieldActivated = isActivated;
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(isActivated, cb::onRfFieldActivated, ex));
+        }
+
+        @Override
+        public void onRfDiscoveryStarted(boolean isDiscoveryStarted) throws RemoteException {
+            mRfDiscoveryStarted = isDiscoveryStarted;
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(isDiscoveryStarted, cb::onRfDiscoveryStarted, ex));
+        }
+
+        @Override
+        public void onStateUpdated(int state) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(state, cb::onStateUpdated, ex));
+        }
+
+        @Override
+        public void onApplyRouting(ResultReceiver isSkipped) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(
+                        new ReceiverWrapper(isSkipped), cb::onApplyRouting, ex));
+        }
+        @Override
+        public void onNdefRead(ResultReceiver isSkipped) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(
+                        new ReceiverWrapper(isSkipped), cb::onNdefRead, ex));
+        }
+        @Override
+        public void onEnable(ResultReceiver isAllowed) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(
+                        new ReceiverWrapper(isAllowed), cb::onEnable, ex));
+        }
+        @Override
+        public void onDisable(ResultReceiver isAllowed) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(
+                        new ReceiverWrapper(isAllowed), cb::onDisable, ex));
+        }
+        @Override
+        public void onBootStarted() throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(null, (Object input) -> cb.onBootStarted(), ex));
+        }
+        @Override
+        public void onEnableStarted() throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(null, (Object input) -> cb.onEnableStarted(), ex));
+        }
+        @Override
+        public void onDisableStarted() throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(null, (Object input) -> cb.onDisableStarted(), ex));
+        }
+        @Override
+        public void onBootFinished(int status) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(status, cb::onBootFinished, ex));
+        }
+        @Override
+        public void onEnableFinished(int status) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(status, cb::onEnableFinished, ex));
+        }
+        @Override
+        public void onDisableFinished(int status) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(status, cb::onDisableFinished, ex));
+        }
+        @Override
+        public void onTagDispatch(ResultReceiver isSkipped) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(
+                        new ReceiverWrapper(isSkipped), cb::onTagDispatch, ex));
+        }
+        @Override
+        public void onRoutingChanged() throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(null, (Object input) -> cb.onRoutingChanged(), ex));
+        }
+        @Override
+        public void onHceEventReceived(int action) throws RemoteException {
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(action, cb::onHceEventReceived, ex));
+        }
+
+        private <T> void handleVoidCallback(
+                T input, Consumer<T> callbackMethod, Executor executor) {
             synchronized (mLock) {
-                if (mCallback == null || mExecutor == null) {
-                    return;
-                }
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    mExecutor.execute(() -> mCallback.onTagConnected(connected, tag));
+                    executor.execute(() -> callbackMethod.accept(input));
+                } catch (RuntimeException ex) {
+                    throw ex;
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
         }
-        @Override
-        public void onStateUpdated(int state) throws RemoteException {
-            handleVoidCallback(state, mCallback::onStateUpdated);
-        }
-        @Override
-        public void onApplyRouting(ResultReceiver isSkipped) throws RemoteException {
-            handleVoidCallback(
-                    new ReceiverWrapper(isSkipped), mCallback::onApplyRouting);
-        }
-        @Override
-        public void onNdefRead(ResultReceiver isSkipped) throws RemoteException {
-            handleVoidCallback(
-                    new ReceiverWrapper(isSkipped), mCallback::onNdefRead);
-        }
-        @Override
-        public void onEnable(ResultReceiver isAllowed) throws RemoteException {
-            handleVoidCallback(
-                    new ReceiverWrapper(isAllowed), mCallback::onEnable);
-        }
-        @Override
-        public void onDisable(ResultReceiver isAllowed) throws RemoteException {
-            handleVoidCallback(
-                    new ReceiverWrapper(isAllowed), mCallback::onDisable);
-        }
-        @Override
-        public void onBootStarted() throws RemoteException {
-            handleVoidCallback(null, (Object input) -> mCallback.onBootStarted());
-        }
-        @Override
-        public void onEnableStarted() throws RemoteException {
-            handleVoidCallback(null, (Object input) -> mCallback.onEnableStarted());
-        }
-        @Override
-        public void onDisableStarted() throws RemoteException {
-            handleVoidCallback(null, (Object input) -> mCallback.onDisableStarted());
-        }
-        @Override
-        public void onBootFinished(int status) throws RemoteException {
-            handleVoidCallback(status, mCallback::onBootFinished);
-        }
-        @Override
-        public void onEnableFinished(int status) throws RemoteException {
-            handleVoidCallback(status, mCallback::onEnableFinished);
-        }
-        @Override
-        public void onDisableFinished(int status) throws RemoteException {
-            handleVoidCallback(status, mCallback::onDisableFinished);
-        }
-        @Override
-        public void onTagDispatch(ResultReceiver isSkipped) throws RemoteException {
-            handleVoidCallback(
-                    new ReceiverWrapper(isSkipped), mCallback::onTagDispatch);
-        }
-        @Override
-        public void onRoutingChanged() throws RemoteException {
-            handleVoidCallback(null, (Object input) -> mCallback.onRoutingChanged());
-        }
-        @Override
-        public void onHceEventReceived(int action) throws RemoteException {
-            handleVoidCallback(action, mCallback::onHceEventReceived);
-        }
 
-        private <T> void handleVoidCallback(T input, Consumer<T> callbackMethod) {
+        private <T1, T2> void handleVoid2ArgCallback(
+                T1 input1, T2 input2, BiConsumer<T1, T2> callbackMethod, Executor executor) {
             synchronized (mLock) {
-                if (mCallback == null || mExecutor == null) {
-                    return;
-                }
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    mExecutor.execute(() -> callbackMethod.accept(input));
+                    executor.execute(() -> callbackMethod.accept(input1, input2));
+                } catch (RuntimeException ex) {
+                    throw ex;
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
@@ -415,17 +519,12 @@
         private <S, T> S handleNonVoidCallbackWithInput(
                 S defaultValue, T input, Function<T, S> callbackMethod) throws RemoteException {
             synchronized (mLock) {
-                if (mCallback == null) {
-                    return defaultValue;
-                }
                 final long identity = Binder.clearCallingIdentity();
                 S result = defaultValue;
                 try {
                     ExecutorService executor = Executors.newSingleThreadExecutor();
-                    FutureTask<S> futureTask = new FutureTask<>(
-                            () -> callbackMethod.apply(input)
-                    );
-                    executor.submit(futureTask);
+                    FutureTask<S> futureTask = new FutureTask<>(() -> callbackMethod.apply(input));
+                    var unused = executor.submit(futureTask);
                     try {
                         result = futureTask.get(
                                 OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS);
@@ -447,17 +546,12 @@
         private <T> T handleNonVoidCallbackWithoutInput(T defaultValue, Supplier<T> callbackMethod)
                 throws RemoteException {
             synchronized (mLock) {
-                if (mCallback == null) {
-                    return defaultValue;
-                }
                 final long identity = Binder.clearCallingIdentity();
                 T result = defaultValue;
                 try {
                     ExecutorService executor = Executors.newSingleThreadExecutor();
-                    FutureTask<T> futureTask = new FutureTask<>(
-                            callbackMethod::get
-                    );
-                    executor.submit(futureTask);
+                    FutureTask<T> futureTask = new FutureTask<>(callbackMethod::get);
+                    var unused = executor.submit(futureTask);
                     try {
                         result = futureTask.get(
                                 OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS);
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/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 0fda91d..0ade4db 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -141,3 +141,11 @@
     description: "Enable override and recover routing table"
     bug: "329043523"
 }
+
+flag {
+    name: "nfc_watchdog"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable watchdog for the NFC system process"
+    bug: "362937338"
+}
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index a0b3469..08155dd 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -133,7 +133,8 @@
                 <LinearLayout
                     android:id="@+id/negative_multiple_devices_layout"
                     android:layout_width="wrap_content"
-                    android:layout_height="48dp"
+                    android:layout_height="match_parent"
+                    android:padding="6dp"
                     android:gravity="center"
                     android:visibility="gone">
 
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index e8e24f4..fe7cfc6 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -103,11 +103,10 @@
     <style name="NegativeButtonMultipleDevices"
            parent="@android:style/Widget.Material.Button.Colored">
         <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">36dp</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:minHeight">36dp</item>>
         <item name="android:textAllCaps">false</item>
         <item name="android:textSize">14sp</item>
-        <item name="android:paddingLeft">6dp</item>
-        <item name="android:paddingRight">6dp</item>
         <item name="android:background">@drawable/btn_negative_multiple_devices</item>
         <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
     </style>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 6117330..7974a37 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -19,6 +19,7 @@
 import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
 import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
 import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
 import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
@@ -33,6 +34,7 @@
 import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES;
+import static com.android.companiondevicemanager.Utils.RESULT_CODE_TO_REASON;
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
@@ -51,6 +53,7 @@
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
+import android.companion.Flags;
 import android.companion.IAssociationRequestCallback;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -231,7 +234,7 @@
         boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false);
         if (forCancelDialog) {
             Slog.i(TAG, "Cancelling the user confirmation");
-            cancel(RESULT_CANCELED);
+            cancel(RESULT_CANCELED, null);
             return;
         }
 
@@ -243,10 +246,15 @@
         if (appCallback == null) {
             return;
         }
-        Slog.e(TAG, "More than one AssociationRequests are processing.");
 
         try {
-            appCallback.onFailure(RESULT_INTERNAL_ERROR);
+            if (Flags.associationFailureCode()) {
+                appCallback.onFailure(
+                        RESULT_SECURITY_ERROR, "More than one AssociationRequests are processing.");
+            } else {
+                appCallback.onFailure(
+                        RESULT_INTERNAL_ERROR, "More than one AssociationRequests are processing.");
+            }
         } catch (RemoteException ignore) {
         }
     }
@@ -257,7 +265,7 @@
 
         // TODO: handle config changes without cancelling.
         if (!isDone()) {
-            cancel(RESULT_CANCELED); // will finish()
+            cancel(RESULT_CANCELED, null); // will finish()
         }
     }
 
@@ -331,7 +339,7 @@
                 && CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
             synchronized (LOCK) {
                 if (sDiscoveryStarted) {
-                    cancel(RESULT_DISCOVERY_TIMEOUT);
+                    cancel(RESULT_DISCOVERY_TIMEOUT, null);
                 }
             }
         }
@@ -371,7 +379,7 @@
         mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
     }
 
-    private void cancel(int failureCode) {
+    private void cancel(int errorCode, @Nullable CharSequence error) {
         if (isDone()) {
             Slog.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
             return;
@@ -385,13 +393,14 @@
 
         // First send callback to the app directly...
         try {
-            Slog.i(TAG, "Sending onFailure to app due to failureCode=" + failureCode);
-            mAppCallback.onFailure(failureCode);
+            CharSequence errorMessage = error != null
+                    ? error : RESULT_CODE_TO_REASON.get(errorCode);
+            mAppCallback.onFailure(errorCode, errorMessage);
         } catch (RemoteException ignore) {
         }
 
         // ... then set result and finish ("sending" onActivityResult()).
-        setResultAndFinish(null, failureCode);
+        setResultAndFinish(null, errorCode);
     }
 
     private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) {
@@ -436,7 +445,7 @@
             }
         } catch (PackageManager.NameNotFoundException e) {
             Slog.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
-            cancel(RESULT_INTERNAL_ERROR);
+            cancel(RESULT_INTERNAL_ERROR, e.getMessage());
             return;
         }
 
@@ -625,7 +634,7 @@
         // Disable the button, to prevent more clicks.
         v.setEnabled(false);
 
-        cancel(RESULT_USER_REJECTED);
+        cancel(RESULT_USER_REJECTED, null);
     }
 
     private void onShowHelperDialog(View view) {
@@ -755,8 +764,8 @@
             };
 
     @Override
-    public void onShowHelperDialogFailed() {
-        cancel(RESULT_INTERNAL_ERROR);
+    public void onShowHelperDialogFailed(CharSequence errorMessage) {
+        cancel(RESULT_INTERNAL_ERROR, errorMessage);
     }
 
     @Override
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index ec92987..b2d78da 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -54,7 +54,7 @@
     private Button mButton;
 
     interface CompanionVendorHelperDialogListener {
-        void onShowHelperDialogFailed();
+        void onShowHelperDialogFailed(CharSequence error);
         void onHelperDialogDismissed();
     }
 
@@ -110,7 +110,7 @@
             appLabel = getApplicationLabel(getContext(), packageName, userId);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
-            mListener.onShowHelperDialogFailed();
+            mListener.onShowHelperDialogFailed(e.getMessage());
             return;
         }
 
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
index 8c14f80..2f97132 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -16,6 +16,15 @@
 
 package com.android.companiondevicemanager;
 
+import static android.companion.CompanionDeviceManager.REASON_CANCELED;
+import static android.companion.CompanionDeviceManager.REASON_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.REASON_USER_REJECTED;
+import static android.companion.CompanionDeviceManager.RESULT_CANCELED;
+import static android.companion.CompanionDeviceManager.RESULT_DISCOVERY_TIMEOUT;
+import static android.companion.CompanionDeviceManager.RESULT_USER_REJECTED;
+
+import static java.util.Collections.unmodifiableMap;
+
 import android.annotation.NonNull;
 import android.annotation.StringRes;
 import android.content.Context;
@@ -31,6 +40,9 @@
 import android.os.ResultReceiver;
 import android.text.Html;
 import android.text.Spanned;
+import android.util.ArrayMap;
+
+import java.util.Map;
 
 /**
  * Utilities.
@@ -40,6 +52,17 @@
             "android.companion.vendor_icon";
     private static final String COMPANION_DEVICE_ACTIVITY_VENDOR_NAME =
             "android.companion.vendor_name";
+    // This map solely the common error messages that occur during the Association
+    // creation process.
+    static final Map<Integer, String> RESULT_CODE_TO_REASON;
+    static {
+        final Map<Integer, String> map = new ArrayMap<>();
+        map.put(RESULT_CANCELED, REASON_CANCELED);
+        map.put(RESULT_USER_REJECTED, REASON_USER_REJECTED);
+        map.put(RESULT_DISCOVERY_TIMEOUT, REASON_DISCOVERY_TIMEOUT);
+
+        RESULT_CODE_TO_REASON = unmodifiableMap(map);
+    }
 
     /**
      * Convert an instance of a "locally-defined" ResultReceiver to an instance of
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/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index e58de64..5bd26e113 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -48,45 +48,14 @@
 /* Used as credential suggestion or user action chip. */
 @Composable
 fun CredentialsScreenChip(
-    label: String,
+    primaryText: @Composable () -> Unit,
+    secondaryText: (@Composable () -> Unit)? = null,
     onClick: () -> Unit,
-    secondaryLabel: String? = null,
     icon: Drawable? = null,
     isAuthenticationEntryLocked: Boolean? = null,
-    textAlign: TextAlign = TextAlign.Center,
     modifier: Modifier = Modifier,
     colors: ChipColors = ChipDefaults.secondaryChipColors()
 ) {
-        return CredentialsScreenChip(
-                    onClick,
-                    text = {
-                        WearButtonText(
-                            text = label,
-                            textAlign = textAlign,
-                            maxLines = 2
-                        )
-                    },
-                    secondaryLabel,
-                    icon,
-                    isAuthenticationEntryLocked,
-                    modifier,
-                    colors
-        )
-}
-
-
-
-/* Used as credential suggestion or user action chip. */
-@Composable
-fun CredentialsScreenChip(
-    onClick: () -> Unit,
-    text: @Composable () -> Unit,
-    secondaryLabel: String? = null,
-    icon: Drawable? = null,
-    isAuthenticationEntryLocked: Boolean? = null,
-    modifier: Modifier = Modifier,
-    colors: ChipColors = ChipDefaults.primaryChipColors(),
-    ) {
     val labelParam: (@Composable RowScope.() -> Unit) =
         {
             var horizontalArrangement = Arrangement.Start
@@ -94,19 +63,15 @@
                 horizontalArrangement = Arrangement.Center
             }
             Row(horizontalArrangement = horizontalArrangement, modifier = modifier.fillMaxWidth()) {
-                text()
+                primaryText()
             }
         }
 
     val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
-        secondaryLabel?.let {
+        secondaryText?.let {
             {
                 Row {
-                    WearSecondaryLabel(
-                        text = secondaryLabel,
-                        color = WearMaterialTheme.colors.onSurfaceVariant
-                    )
-
+                    secondaryText()
                     if (isAuthenticationEntryLocked != null) {
                         if (isAuthenticationEntryLocked) {
                             Icon(
@@ -156,9 +121,19 @@
 @Composable
 fun CredentialsScreenChipPreview() {
     CredentialsScreenChip(
-        label = "Elisa Beckett",
+        primaryText = {
+            WearButtonText(
+                text = "Elisa Beckett",
+                textAlign = TextAlign.Start,
+            )
+        },
         onClick = { },
-        secondaryLabel = "beckett_bakery@gmail.com",
+        secondaryText = {
+            WearSecondaryLabel(
+                text = "beckett_bakery@gmail.com",
+                color = WearMaterialTheme.colors.onSurfaceVariant
+            )
+        },
         icon = null,
     )
 }
@@ -166,8 +141,13 @@
 @Composable
 fun SignInOptionsChip(onClick: () -> Unit) {
     CredentialsScreenChip(
-        label = stringResource(R.string.dialog_sign_in_options_button),
-        textAlign = TextAlign.Start,
+        primaryText = {
+            WearButtonText(
+                text = stringResource(R.string.dialog_sign_in_options_button),
+                textAlign = TextAlign.Center,
+                maxLines = 2
+            )
+        },
         onClick = onClick,
     )
 }
@@ -182,7 +162,7 @@
 fun ContinueChip(onClick: () -> Unit) {
     CredentialsScreenChip(
         onClick = onClick,
-        text = {
+        primaryText = {
             WearButtonText(
                 text = stringResource(R.string.dialog_continue_button),
                 textAlign = TextAlign.Center,
@@ -202,14 +182,21 @@
 @Composable
 fun DismissChip(onClick: () -> Unit) {
     CredentialsScreenChip(
-        label = stringResource(R.string.dialog_dismiss_button),
+        primaryText = {
+            WearButtonText(
+                text = stringResource(R.string.dialog_dismiss_button),
+                textAlign = TextAlign.Center,
+                maxLines = 2
+            )
+        },
         onClick = onClick,
     )
 }
 @Composable
 fun LockedProviderChip(
     authenticationEntryInfo: AuthenticationEntryInfo,
-    onClick: () -> Unit
+    secondaryMaxLines: Int = 1,
+    onClick: () -> Unit,
 ) {
     val secondaryLabel = stringResource(
         if (authenticationEntryInfo.isUnlockedAndEmpty)
@@ -218,10 +205,21 @@
     )
 
     CredentialsScreenChip(
-        label = authenticationEntryInfo.title,
+        primaryText = {
+            WearButtonText(
+                text = authenticationEntryInfo.title,
+                textAlign = TextAlign.Start,
+                maxLines = 2,
+            )
+        },
         icon = authenticationEntryInfo.icon,
-        secondaryLabel = secondaryLabel,
-        textAlign = TextAlign.Start,
+        secondaryText = {
+            WearSecondaryLabel(
+                text = secondaryLabel,
+                color = WearMaterialTheme.colors.onSurfaceVariant,
+                maxLines = secondaryMaxLines
+                )
+        },
         isAuthenticationEntryLocked = !authenticationEntryInfo.isUnlockedAndEmpty,
         onClick = onClick,
     )
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
index a7b13ad..a1dc568 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/Texts.kt
@@ -16,7 +16,6 @@
 
 package com.android.credentialmanager.common.ui.components
 
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material3.Text
@@ -93,15 +92,16 @@
 fun WearSecondaryLabel(
     text: String,
     color: Color = WearMaterialTheme.colors.onSurface,
-    modifier: Modifier = Modifier
+    modifier: Modifier = Modifier,
+    maxLines: Int = 1,
 ) {
     Text(
-        modifier = modifier.fillMaxSize(),
+        modifier = modifier.wrapContentSize(),
         text = text,
         color = color,
         style = WearMaterialTheme.typography.caption1,
         overflow = TextOverflow.Ellipsis,
         textAlign = TextAlign.Start,
-        maxLines = 1,
+        maxLines = maxLines,
     )
 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index ef32c94..932b345 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -24,13 +24,13 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.Alignment
 import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
 import com.android.credentialmanager.FlowEngine
 import com.android.credentialmanager.R
 import com.android.credentialmanager.common.ui.components.WearButtonText
+import com.android.credentialmanager.ui.components.LockedProviderChip
 import com.android.credentialmanager.common.ui.components.WearSecondaryLabel
 import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.ui.components.CredentialsScreenChipSpacer
@@ -38,6 +38,8 @@
 import com.google.android.horologist.compose.layout.ScalingLazyColumn
 import com.google.android.horologist.compose.layout.rememberColumnState
 import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
+import androidx.compose.ui.text.style.TextAlign
+import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
 
 /**
  * Screen that shows multiple credentials to select from, grouped by accounts
@@ -69,6 +71,7 @@
                     text = stringResource(R.string.sign_in_options_title),
                     textAlign = TextAlign.Center,
                     modifier = Modifier.weight(0.854f).fillMaxSize(),
+                    maxLines = 2,
                 )
                 Spacer(Modifier.weight(0.073f)) // 7.3% side margin
             }
@@ -94,19 +97,39 @@
             userNameEntries.sortedCredentialEntryList.forEach { credential: CredentialEntryInfo ->
                 item {
                     CredentialsScreenChip(
-                        label = credential.userName,
+                        primaryText = {
+                            WearButtonText(
+                                text = credential.userName,
+                                textAlign = TextAlign.Start,
+                                maxLines = 2,
+                            )
+                        },
                         onClick = { selectEntry(credential, false) },
-                        secondaryLabel =
-                        credential.credentialTypeDisplayName.ifEmpty {
-                            credential.providerDisplayName
+                        secondaryText =
+                        {
+                            WearSecondaryLabel(
+                                text = credential.credentialTypeDisplayName.ifEmpty {
+                                    credential.providerDisplayName
+                                },
+                                color = WearMaterialTheme.colors.onSurfaceVariant,
+                                maxLines = 2
+                            )
                         },
                         icon = credential.icon,
-                        textAlign = TextAlign.Start
                     )
 
                     CredentialsScreenChipSpacer()
                 }
             }
+
+            credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
+                item {
+                    LockedProviderChip(authenticationEntryInfo, secondaryMaxLines = 2) {
+                        selectEntry(authenticationEntryInfo, false)
+                    }
+                    CredentialsScreenChipSpacer()
+                }
+            }
         }
 
         if (credentialSelectorUiState.actionEntryList.isNotEmpty()) {
@@ -120,7 +143,8 @@
                             bottom = 4.dp,
                             start = 0.dp,
                             end = 0.dp
-                        ).fillMaxWidth(0.87f)
+                        ).fillMaxWidth(0.87f),
+                        maxLines = 2
                 )
                     Spacer(Modifier.weight(0.0624f)) // 6.24% side margin
                 }
@@ -128,9 +152,15 @@
             credentialSelectorUiState.actionEntryList.forEach { actionEntry ->
                 item {
                     CredentialsScreenChip(
-                        label = actionEntry.title,
+                        primaryText = {
+                            WearButtonText(
+                                text = actionEntry.title,
+                                textAlign = TextAlign.Start,
+                                maxLines = 2
+                            )
+                        },
                         onClick = { selectEntry(actionEntry, false) },
-                        secondaryLabel = null,
+                        secondaryText = null,
                         icon = actionEntry.icon,
                     )
                     CredentialsScreenChipSpacer()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index 38307b0..b56b982b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -17,7 +17,6 @@
 package com.android.credentialmanager.ui.screens.multiple
 
 import androidx.compose.foundation.layout.Row
-import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.foundation.layout.fillMaxSize
 import com.android.credentialmanager.R
 import androidx.compose.ui.res.stringResource
@@ -40,6 +39,10 @@
 import com.android.credentialmanager.model.CredentialType
 import com.android.credentialmanager.ui.components.BottomSpacer
 import com.android.credentialmanager.ui.components.CredentialsScreenChipSpacer
+import com.android.credentialmanager.common.ui.components.WearButtonText
+import com.android.credentialmanager.common.ui.components.WearSecondaryLabel
+import androidx.compose.ui.text.style.TextAlign
+import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
 
 /**
  * Screen that shows multiple credentials to select from.
@@ -82,14 +85,25 @@
             credentials.forEach { credential: CredentialEntryInfo ->
                 item {
                     CredentialsScreenChip(
-                        label = credential.userName,
+                        primaryText =
+                        {
+                            WearButtonText(
+                                text = credential.userName,
+                                textAlign = TextAlign.Start,
+                                maxLines = 2
+                            )
+                        },
                         onClick = { selectEntry(credential, false) },
-                        secondaryLabel =
-                        credential.credentialTypeDisplayName.ifEmpty {
-                            credential.providerDisplayName
+                        secondaryText = {
+                            WearSecondaryLabel(
+                                text = credential.credentialTypeDisplayName.ifEmpty {
+                                    credential.providerDisplayName
+                                },
+                                color = WearMaterialTheme.colors.onSurfaceVariant,
+                                maxLines = 1 // See b/359649621 for context
+                            )
                         },
                         icon = credential.icon,
-                        textAlign = TextAlign.Start
                     )
                     CredentialsScreenChipSpacer()
                 }
diff --git a/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
index ac47fbd..391b16d 100644
--- a/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
+++ b/packages/EasterEgg/src/com/android/egg/paint/PaintActivity.java
@@ -23,7 +23,6 @@
 
 import android.app.Activity;
 import android.content.res.Configuration;
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.view.MotionEvent;
@@ -38,9 +37,7 @@
 
 import com.android.egg.R;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.stream.IntStream;
 
 public class PaintActivity extends Activity {
@@ -60,31 +57,28 @@
     private View.OnClickListener buttonHandler = new View.OnClickListener() {
         @Override
         public void onClick(View view) {
-            switch (view.getId()) {
-                case R.id.btnBrush:
-                    view.setSelected(true);
-                    hideToolbar(colors);
-                    toggleToolbar(brushes);
-                    break;
-                case R.id.btnColor:
-                    view.setSelected(true);
-                    hideToolbar(brushes);
-                    toggleToolbar(colors);
-                    break;
-                case R.id.btnClear:
-                    painting.clear();
-                    break;
-                case R.id.btnSample:
-                    sampling = true;
-                    view.setSelected(true);
-                    break;
-                case R.id.btnZen:
-                    painting.setZenMode(!painting.getZenMode());
-                    view.animate()
-                            .setStartDelay(200)
-                            .setInterpolator(new OvershootInterpolator())
-                            .rotation(painting.getZenMode() ? 0f : 90f);
-                    break;
+            // With non final fields in the R class we can't switch on the
+            // id since the case values are no longer constants.
+            int viewId = view.getId();
+            if (viewId == R.id.btnBrush) {
+                view.setSelected(true);
+                hideToolbar(colors);
+                toggleToolbar(brushes);
+            } else if (viewId == R.id.btnColor) {
+                view.setSelected(true);
+                hideToolbar(brushes);
+                toggleToolbar(colors);
+            } else if (viewId == R.id.btnClear) {
+                painting.clear();
+            } else if (viewId == R.id.btnSample) {
+                sampling = true;
+                view.setSelected(true);
+            } else if (viewId == R.id.btnZen) {
+                painting.setZenMode(!painting.getZenMode());
+                view.animate()
+                        .setStartDelay(200)
+                        .setInterpolator(new OvershootInterpolator())
+                        .rotation(painting.getZenMode() ? 0f : 90f);
             }
         }
     };
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/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index a543450..3011ce0 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.7.0-beta07"
+    extra["jetpackComposeVersion"] = "1.7.0-rc01"
 }
 
 subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 3507605..d01c0b9 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.5.2"
+agp = "8.6.0"
 compose-compiler = "1.5.11"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10-bin.zip
new file mode 100644
index 0000000..50432f3
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.10-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip
deleted file mode 100644
index 9a97e46..0000000
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
index 2c35211..a4b76b9 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 9f29c77..9a7f4b6 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,6 +16,6 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=gradle-8.9-bin.zip
+distributionUrl=gradle-8.10-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index e9153e3..f0c2ea6 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,13 +54,13 @@
 dependencies {
     api(project(":SettingsLibColor"))
     api("androidx.appcompat:appcompat:1.7.0")
-    api("androidx.compose.material3:material3:1.3.0-beta05")
+    api("androidx.compose.material3:material3:1.3.0-rc01")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.8.0-beta07")
+    api("androidx.navigation:navigation-compose:2.8.0-rc01")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.11.0")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
index aceb545..62af08e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -117,7 +117,7 @@
     val indication = LocalIndication.current
     val onChangeWithLog = wrapOnSwitchWithLog(onCheckedChange)
     val interactionSource = remember { MutableInteractionSource() }
-    val modifier = remember(checked, changeable) {
+    val modifier =
         if (checked != null && onChangeWithLog != null) {
             Modifier.toggleable(
                 value = checked,
@@ -128,7 +128,6 @@
                 onValueChange = onChangeWithLog,
             )
         } else Modifier
-    }
     BasePreference(
         title = title,
         summary = summary,
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 2b3862f..34b597b 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -129,3 +129,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "member_device_lea_active_state_sync_fix"
+    namespace: "cross_device_experiences"
+    description: "Gates whether to enable fix for member device active state sync on lea profile"
+    bug: "364201289"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
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/SystemUI/res/color/brightness_slider_track.xml b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
similarity index 65%
copy from packages/SystemUI/res/color/brightness_slider_track.xml
copy to packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
index 6028769..18696c6 100644
--- a/packages/SystemUI/res/color/brightness_slider_track.xml
+++ b/packages/SettingsLib/res/drawable/audio_sharing_rounded_bg_ripple.xml
@@ -1,5 +1,6 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright (C) 2024 The Android Open Source Project
+<?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.
@@ -14,6 +15,7 @@
   ~ limitations under the License.
   -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:color="@android:color/system_neutral2_500" android:lStar="40" />
-</selector>
\ No newline at end of file
+<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-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index f739410..a0724b0 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -130,7 +130,7 @@
     <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"સંપર્કો અને કૉલ ઇતિહાસ ઍક્સેસ કરવા દો"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"માહિતીનો ઉપયોગ કૉલની ઘોષણાઓ અને વધુ બાબતો માટે કરવામાં આવશે"</string>
     <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"ઇન્ટરનેટ કનેક્શન શેરિંગ"</string>
-    <string name="bluetooth_profile_map" msgid="8907204701162107271">"ટેક્સ્ટ સંદેશા"</string>
+    <string name="bluetooth_profile_map" msgid="8907204701162107271">"ટેક્સ્ટ મેસેજ"</string>
     <string name="bluetooth_profile_sap" msgid="8304170950447934386">"સિમ ઍક્સેસ"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD ઑડિયો: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD ઑડિયો"</string>
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/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 68b81db..3c3de04 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -31,4 +31,14 @@
     <!-- Control whether status bar should distinguish HSPA data icon form UMTS
     data icon on devices -->
     <bool name="config_hspa_data_distinguishable">false</bool>
+
+    <!-- Edit User avatar explicit package name -->
+    <string name="config_avatar_picker_package" translatable="false">
+        com.android.avatarpicker
+    </string>
+
+    <!-- Edit User avatar explicit activity class -->
+    <string name="config_avatar_picker_class" translatable="false">
+        com.android.avatarpicker.ui.AvatarPickerActivity
+    </string>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 687c728..4d771c0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1364,6 +1364,9 @@
     <!-- Keywords for setting screen for controlling apps that can schedule alarms [CHAR LIMIT=100] -->
     <string name="keywords_alarms_and_reminders">schedule, alarm, reminder, clock</string>
 
+    <!-- Priority Modes: Name of the "manual" Do Not Disturb mode. [CHAR LIMIT=50] -->
+    <string name="zen_mode_do_not_disturb_name">Do Not Disturb</string>
+
     <!-- Sound: Title for the Do not Disturb option and associated settings page. [CHAR LIMIT=50]-->
     <string name="zen_mode_settings_title">Do Not Disturb</string>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index b02b0c4..1e58335 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -495,7 +495,7 @@
                 || packageName.equals(sServicesSystemSharedLibPackageName)
                 || packageName.equals(sSharedSystemSharedLibPackageName)
                 || packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
-                || (updateServiceV2() && packageName.equals(getDefaultWebViewPackageName()))
+                || (updateServiceV2() && packageName.equals(getDefaultWebViewPackageName(pm)))
                 || isDeviceProvisioningPackage(resources, packageName);
     }
 
@@ -511,7 +511,7 @@
 
     /** Fetch the package name of the default WebView provider. */
     @Nullable
-    private static String getDefaultWebViewPackageName() {
+    private static String getDefaultWebViewPackageName(PackageManager pm) {
         if (sDefaultWebViewPackageName != null) {
             return sDefaultWebViewPackageName;
         }
@@ -519,9 +519,10 @@
         WebViewProviderInfo provider = null;
 
         if (android.webkit.Flags.updateServiceIpcWrapper()) {
-            WebViewUpdateManager manager = WebViewUpdateManager.getInstance();
-            if (manager != null) {
-                provider = manager.getDefaultWebViewPackage();
+            if (pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+                // WebViewUpdateManager.getInstance() will not return null on devices with
+                // FEATURE_WEBVIEW.
+                provider = WebViewUpdateManager.getInstance().getDefaultWebViewPackage();
             }
         } else {
             try {
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/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.aidl
similarity index 84%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
copy to packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.aidl
index 3c5beeb..1726036 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.settingslib.bluetooth.devicesettings;
 
-parcelable BubbleBarLocation;
\ No newline at end of file
+parcelable DeviceSettingsProviderServiceStatus;
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.kt
new file mode 100644
index 0000000..977849e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A data class representing a device settings item in bluetooth device details config.
+ *
+ * @property enabled Whether the service is enabled.
+ * @property extras Extra bundle
+ */
+data class DeviceSettingsProviderServiceStatus(
+    val enabled: Boolean,
+    val extras: Bundle = Bundle.EMPTY,
+) : Parcelable {
+
+    override fun describeContents(): Int = 0
+
+    override fun writeToParcel(parcel: Parcel, flags: Int) {
+        parcel.run {
+            writeBoolean(enabled)
+            writeBundle(extras)
+        }
+    }
+
+    companion object {
+        @JvmField
+        val CREATOR: Parcelable.Creator<DeviceSettingsProviderServiceStatus> =
+            object : Parcelable.Creator<DeviceSettingsProviderServiceStatus> {
+                override fun createFromParcel(parcel: Parcel) =
+                    parcel.run {
+                        DeviceSettingsProviderServiceStatus(
+                            enabled = readBoolean(),
+                            extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
+                        )
+                    }
+
+                override fun newArray(size: Int): Array<DeviceSettingsProviderServiceStatus?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl
index d5efac9..1c0a1fd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl
@@ -18,10 +18,12 @@
 
 import com.android.settingslib.bluetooth.devicesettings.DeviceInfo;
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState;
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsProviderServiceStatus;
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener;
 
-oneway interface IDeviceSettingsProviderService {
-   void registerDeviceSettingsListener(in DeviceInfo device, in IDeviceSettingsListener callback);
-   void unregisterDeviceSettingsListener(in DeviceInfo device, in IDeviceSettingsListener callback);
-   void updateDeviceSettings(in DeviceInfo device, in DeviceSettingState params);
+interface IDeviceSettingsProviderService {
+   DeviceSettingsProviderServiceStatus getServiceStatus();
+   oneway void registerDeviceSettingsListener(in DeviceInfo device, in IDeviceSettingsListener callback);
+   oneway void unregisterDeviceSettingsListener(in DeviceInfo device, in IDeviceSettingsListener callback);
+   oneway void updateDeviceSettings(in DeviceInfo device, in DeviceSettingState params);
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/model/ServiceConnectionStatus.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/model/ServiceConnectionStatus.kt
new file mode 100644
index 0000000..25080bc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/model/ServiceConnectionStatus.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.settingslib.bluetooth.devicesettings.data.model
+
+import android.os.IInterface
+
+/** Present a service connection status. */
+sealed interface ServiceConnectionStatus<out T : IInterface> {
+    /** Service is connecting. */
+    data object Connecting : ServiceConnectionStatus<Nothing>
+
+    /** Service is connected. */
+    data class Connected<T : IInterface>(val service: T) : ServiceConnectionStatus<T>
+
+    /** Service connection failed. */
+    data object Failed : ServiceConnectionStatus<Nothing>
+}
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/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index d6b2862..33beb06 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -22,7 +22,8 @@
 import android.content.Intent
 import android.content.ServiceConnection
 import android.os.IBinder
-import com.android.internal.util.ConcurrentUtils
+import android.os.IInterface
+import android.util.Log
 import com.android.settingslib.bluetooth.BluetoothUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.devicesettings.DeviceInfo
@@ -34,27 +35,28 @@
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
+import com.android.settingslib.bluetooth.devicesettings.data.model.ServiceConnectionStatus
 import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.atomic.AtomicReference
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.async
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.emitAll
-import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapConcat
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
@@ -84,64 +86,132 @@
             }
     }
 
-    private var config = AtomicReference<DeviceSettingsConfig?>(null)
-    private var idToSetting = AtomicReference<Flow<Map<Int, DeviceSetting>>?>(null)
-
-    /** Gets [DeviceSettingsConfig] for the device, return null when failed. */
-    suspend fun getDeviceSettingsConfig(): DeviceSettingsConfig? =
-        config.computeIfAbsent {
-            getConfigServiceBindingIntent(cachedDevice)
-                .flatMapLatest { getService(it) }
-                .map { it?.let { IDeviceSettingsConfigProviderService.Stub.asInterface(it) } }
-                .map {
-                    it?.getDeviceSettingsConfig(
-                        deviceInfo { setBluetoothAddress(cachedDevice.address) }
-                    )
+    private var isServiceEnabled =
+        coroutineScope.async(backgroundCoroutineContext, start = CoroutineStart.LAZY) {
+            val states = getSettingsProviderServices()?.values ?: return@async false
+            combine(states) { it.toList() }
+                .mapNotNull { allStatus ->
+                    if (allStatus.any { it is ServiceConnectionStatus.Failed }) {
+                        false
+                    } else if (allStatus.all { it is ServiceConnectionStatus.Connected }) {
+                        allStatus
+                            .filterIsInstance<
+                                ServiceConnectionStatus.Connected<IDeviceSettingsProviderService>
+                            >()
+                            .all { it.service.serviceStatus?.enabled == true }
+                    } else {
+                        null
+                    }
                 }
                 .first()
         }
 
+    private var config =
+        coroutineScope.async(backgroundCoroutineContext, start = CoroutineStart.LAZY) {
+            val intent =
+                tryGetEndpointFromMetadata(cachedDevice)?.toIntent()
+                    ?: run {
+                        Log.i(TAG, "Unable to read device setting metadata from $cachedDevice")
+                        return@async null
+                    }
+            getService(intent, IDeviceSettingsConfigProviderService.Stub::asInterface)
+                .flatMapConcat {
+                    when (it) {
+                        is ServiceConnectionStatus.Connected ->
+                            flowOf(
+                                it.service.getDeviceSettingsConfig(
+                                    deviceInfo { setBluetoothAddress(cachedDevice.address) }
+                                )
+                            )
+                        ServiceConnectionStatus.Connecting -> flowOf()
+                        ServiceConnectionStatus.Failed -> flowOf(null)
+                    }
+                }
+                .first()
+        }
+
+    private val settingIdToItemMapping =
+        flow {
+                if (!isServiceEnabled.await()) {
+                    Log.w(TAG, "Service is disabled")
+                    return@flow
+                }
+                getSettingsProviderServices()
+                    ?.values
+                    ?.map {
+                        it.flatMapLatest { status ->
+                            when (status) {
+                                is ServiceConnectionStatus.Connected ->
+                                    getDeviceSettingsFromService(cachedDevice, status.service)
+                                else -> flowOf(emptyList())
+                            }
+                        }
+                    }
+                    ?.let { items -> combine(items) { it.toList().flatten() } }
+                    ?.map { items -> items.associateBy { it.settingId } }
+                    ?.let { emitAll(it) }
+            }
+            .shareIn(scope = coroutineScope, started = SharingStarted.WhileSubscribed(), replay = 1)
+
+    /** Gets [DeviceSettingsConfig] for the device, return null when failed. */
+    suspend fun getDeviceSettingsConfig(): DeviceSettingsConfig? {
+        if (!isServiceEnabled.await()) {
+            Log.w(TAG, "Service is disabled")
+            return null
+        }
+        return readConfig()
+    }
+
     /** Gets all device settings for the device. */
     fun getDeviceSettingList(): Flow<List<DeviceSetting>> =
-        getSettingIdToItemMapping().map { it.values.toList() }
+        settingIdToItemMapping.map { it.values.toList() }
 
     /** Gets the device settings with the ID for the device. */
     fun getDeviceSetting(@DeviceSettingId deviceSettingId: Int): Flow<DeviceSetting?> =
-        getSettingIdToItemMapping().map { it[deviceSettingId] }
+        settingIdToItemMapping.map { it[deviceSettingId] }
 
     /** Updates the device setting state for the device. */
     suspend fun updateDeviceSettings(
         @DeviceSettingId deviceSettingId: Int,
         deviceSettingPreferenceState: DeviceSettingPreferenceState,
     ) {
-        getDeviceSettingsConfig()?.let { config ->
+        if (!isServiceEnabled.await()) {
+            Log.w(TAG, "Service is disabled")
+            return
+        }
+        readConfig()?.let { config ->
             (config.mainContentItems + config.moreSettingsItems)
                 .find { it.settingId == deviceSettingId }
                 ?.let {
                     getSettingsProviderServices()
                         ?.get(EndPoint(it.packageName, it.className, it.intentAction))
-                        ?.filterNotNull()
+                        ?.filterIsInstance<
+                            ServiceConnectionStatus.Connected<IDeviceSettingsProviderService>
+                        >()
                         ?.first()
                 }
+                ?.service
                 ?.updateDeviceSettings(
                     deviceInfo { setBluetoothAddress(cachedDevice.address) },
                     DeviceSettingState.Builder()
                         .setSettingId(deviceSettingId)
                         .setPreferenceState(deviceSettingPreferenceState)
-                        .build()
+                        .build(),
                 )
         }
     }
 
+    private suspend fun readConfig(): DeviceSettingsConfig? = config.await()
+
     private suspend fun getSettingsProviderServices():
-        Map<EndPoint, StateFlow<IDeviceSettingsProviderService?>>? =
-        getDeviceSettingsConfig()
+        Map<EndPoint, StateFlow<ServiceConnectionStatus<IDeviceSettingsProviderService>>>? =
+        readConfig()
             ?.let { config ->
                 (config.mainContentItems + config.moreSettingsItems).map {
                     EndPoint(
                         packageName = it.packageName,
                         className = it.className,
-                        intentAction = it.intentAction
+                        intentAction = it.intentAction,
                     )
                 }
             }
@@ -150,43 +220,22 @@
                 { it },
                 { endpoint ->
                     services.computeIfAbsent(endpoint) {
-                        getService(endpoint.toIntent())
-                            .map { service ->
-                                IDeviceSettingsProviderService.Stub.asInterface(service)
-                            }
-                            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+                        getService(
+                                endpoint.toIntent(),
+                                IDeviceSettingsProviderService.Stub::asInterface,
+                            )
+                            .stateIn(
+                                coroutineScope,
+                                SharingStarted.WhileSubscribed(),
+                                ServiceConnectionStatus.Connecting,
+                            )
                     }
-                }
+                },
             )
 
-    private fun getSettingIdToItemMapping(): Flow<Map<Int, DeviceSetting>> =
-        idToSetting.computeIfAbsent {
-            flow {
-                    getSettingsProviderServices()
-                        ?.values
-                        ?.map {
-                            it.flatMapLatest { service ->
-                                if (service != null) {
-                                    getDeviceSettingsFromService(cachedDevice, service)
-                                } else {
-                                    flowOf(emptyList())
-                                }
-                            }
-                        }
-                        ?.let { items -> combine(items) { it.toList().flatten() } }
-                        ?.map { items -> items.associateBy { it.settingId } }
-                        ?.let { emitAll(it) }
-                }
-                .shareIn(
-                    scope = coroutineScope,
-                    started = SharingStarted.WhileSubscribed(),
-                    replay = 1
-                )
-        }!!
-
     private fun getDeviceSettingsFromService(
         cachedDevice: CachedBluetoothDevice,
-        service: IDeviceSettingsProviderService
+        service: IDeviceSettingsProviderService,
     ): Flow<List<DeviceSetting>> {
         return callbackFlow {
                 val listener =
@@ -202,51 +251,28 @@
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
     }
 
-    private fun getService(intent: Intent): Flow<IBinder?> {
+    private fun <T : IInterface> getService(
+        intent: Intent,
+        transform: ((IBinder) -> T),
+    ): Flow<ServiceConnectionStatus<T>> {
         return callbackFlow {
             val serviceConnection =
                 object : ServiceConnection {
                     override fun onServiceConnected(name: ComponentName, service: IBinder) {
-                        launch { send(service) }
+                        launch { send(ServiceConnectionStatus.Connected(transform(service))) }
                     }
 
                     override fun onServiceDisconnected(name: ComponentName?) {
-                        launch { send(null) }
+                        launch { send(ServiceConnectionStatus.Connecting) }
                     }
                 }
             if (!context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) {
-                launch { send(null) }
+                launch { send(ServiceConnectionStatus.Failed) }
             }
             awaitClose { context.unbindService(serviceConnection) }
         }
     }
 
-    private fun getConfigServiceBindingIntent(cachedDevice: CachedBluetoothDevice): Flow<Intent> {
-        return callbackFlow {
-                val listener =
-                    BluetoothAdapter.OnMetadataChangedListener { device, key, _ ->
-                        if (
-                            key == METADATA_FAST_PAIR_CUSTOMIZED_FIELDS &&
-                                cachedDevice.device == device
-                        ) {
-                            launch { tryGetEndpointFromMetadata(cachedDevice)?.let { send(it) } }
-                        }
-                    }
-                bluetoothAdaptor.addOnMetadataChangedListener(
-                    cachedDevice.device,
-                    ConcurrentUtils.DIRECT_EXECUTOR,
-                    listener,
-                )
-                awaitClose {
-                    bluetoothAdaptor.removeOnMetadataChangedListener(cachedDevice.device, listener)
-                }
-            }
-            .onStart { tryGetEndpointFromMetadata(cachedDevice)?.let { emit(it) } }
-            .distinctUntilChanged()
-            .map { it.toIntent() }
-            .flowOn(backgroundCoroutineContext)
-    }
-
     private suspend fun tryGetEndpointFromMetadata(cachedDevice: CachedBluetoothDevice): EndPoint? =
         withContext(backgroundCoroutineContext) {
             val packageName =
@@ -257,29 +283,31 @@
             val className =
                 BluetoothUtils.getFastPairCustomizedField(
                     cachedDevice.device,
-                    CONFIG_SERVICE_CLASS_NAME
+                    CONFIG_SERVICE_CLASS_NAME,
                 ) ?: return@withContext null
             val intentAction =
                 BluetoothUtils.getFastPairCustomizedField(
                     cachedDevice.device,
-                    CONFIG_SERVICE_INTENT_ACTION
+                    CONFIG_SERVICE_INTENT_ACTION,
                 ) ?: return@withContext null
             EndPoint(packageName, className, intentAction)
         }
 
-    private inline fun <T> AtomicReference<T?>.computeIfAbsent(producer: () -> T): T? =
-        get() ?: producer().let { compareAndExchange(null, it) ?: it }
-
     private inline fun deviceInfo(block: DeviceInfo.Builder.() -> Unit): DeviceInfo {
         return DeviceInfo.Builder().apply { block() }.build()
     }
 
     companion object {
+        const val TAG = "DeviceSettingSrvConn"
         const val METADATA_FAST_PAIR_CUSTOMIZED_FIELDS: Int = 25
         const val CONFIG_SERVICE_PACKAGE_NAME = "DEVICE_SETTINGS_CONFIG_PACKAGE_NAME"
         const val CONFIG_SERVICE_CLASS_NAME = "DEVICE_SETTINGS_CONFIG_CLASS"
         const val CONFIG_SERVICE_INTENT_ACTION = "DEVICE_SETTINGS_CONFIG_ACTION"
 
-        val services = ConcurrentHashMap<EndPoint, StateFlow<IDeviceSettingsProviderService?>>()
+        val services =
+            ConcurrentHashMap<
+                EndPoint,
+                StateFlow<ServiceConnectionStatus<IDeviceSettingsProviderService>>,
+            >()
     }
 }
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/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 4371f05..043219a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -41,6 +41,8 @@
     override val modes: Flow<List<ZenMode>>
         get() = mutableModesFlow.asStateFlow()
 
+    override fun getModes(): List<ZenMode> = mutableModesFlow.value
+
     private val activeModesDurations = mutableMapOf<String, Duration?>()
 
     init {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 0ff7f84..7fdbcda 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -59,6 +59,8 @@
     /** A list of all existing priority modes. */
     val modes: Flow<List<ZenMode>>
 
+    fun getModes(): List<ZenMode>
+
     fun activateMode(zenMode: ZenMode, duration: Duration? = null)
 
     fun deactivateMode(zenMode: ZenMode)
@@ -184,6 +186,15 @@
         }
     }
 
+    /**
+     * Gets the current list of [ZenMode] instances according to the backend.
+     *
+     * This is necessary, and cannot be supplanted by making [modes] a StateFlow, because it will be
+     * called whenever we know or suspect that [modes] may not have caught up to the latest data
+     * (such as right after a user switch).
+     */
+    override fun getModes(): List<ZenMode> = backend.modes
+
     override fun activateMode(zenMode: ZenMode, duration: Duration?) {
         backend.activateMode(zenMode, duration)
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index f7492cf..8e0cdf8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
 import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
 
@@ -24,11 +25,13 @@
 import android.content.ComponentName;
 import android.net.Uri;
 import android.service.notification.Condition;
+import android.service.notification.SystemZenRules;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 
 import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import java.util.Random;
@@ -40,13 +43,27 @@
     private ZenModeConfig.ZenRule mConfigZenRule;
 
     public static final ZenMode EXAMPLE = new TestModeBuilder().build();
-    public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(Uri.EMPTY, true);
-    public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(Uri.EMPTY, false);
 
-    public static ZenMode manualDnd(Uri conditionId, boolean isActive) {
+    public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(Uri.EMPTY,
+            INTERRUPTION_FILTER_PRIORITY, true);
+
+    public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(Uri.EMPTY,
+            INTERRUPTION_FILTER_PRIORITY, false);
+
+    @NonNull
+    public static ZenMode manualDnd(@NotificationManager.InterruptionFilter int filter,
+            boolean isActive) {
+        return manualDnd(Uri.EMPTY, filter, isActive);
+    }
+
+    private static ZenMode manualDnd(Uri conditionId,
+            @NotificationManager.InterruptionFilter int filter, boolean isActive) {
         return ZenMode.manualDndMode(
                 new AutomaticZenRule.Builder("Do Not Disturb", conditionId)
-                        .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+                        .setInterruptionFilter(filter)
+                        .setType(AutomaticZenRule.TYPE_OTHER)
+                        .setManualInvocationAllowed(true)
+                        .setPackage(SystemZenRules.PACKAGE_ANDROID)
                         .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
                         .build(),
                 isActive);
@@ -58,7 +75,7 @@
         mId = "rule_" + id;
         mRule = new AutomaticZenRule.Builder("Test Rule #" + id, Uri.parse("rule://" + id))
                 .setPackage("some_package")
-                .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
                 .build();
         mConfigZenRule = new ZenModeConfig.ZenRule();
@@ -134,7 +151,7 @@
             @NotificationManager.InterruptionFilter int interruptionFilter) {
         mRule.setInterruptionFilter(interruptionFilter);
         mConfigZenRule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
-                interruptionFilter, NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+                interruptionFilter, INTERRUPTION_FILTER_PRIORITY);
         return this;
     }
 
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..79dabf0
--- /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_special_dnd);
+
+    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..43c6c50 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,23 @@
 
 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.VisibleForTesting;
+import androidx.annotation.Nullable;
 
 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,100 +49,92 @@
     @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;
 
+    /** Obtains the singleton {@link ZenIconLoader}. */
     public static ZenIconLoader getInstance() {
         if (sInstance == null) {
-            sInstance = new ZenIconLoader();
+            sInstance = new ZenIconLoader(Executors.newFixedThreadPool(4));
         }
         return sInstance;
     }
 
-    private ZenIconLoader() {
-        this(Executors.newFixedThreadPool(4));
-    }
-
-    @VisibleForTesting
-    ZenIconLoader(ExecutorService backgroundExecutor) {
+    /**
+     * Constructs a ZenIconLoader with the specified {@code backgroundExecutor}.
+     *
+     * <p>ZenIconLoader <em>should be a singleton</em>, so this should only be used to instantiate
+     * and provide the singleton instance in a module. If the app doesn't support dependency
+     * injection, use {@link #getInstance} instead.
+     */
+    public ZenIconLoader(@NonNull 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..7b2a284 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -18,11 +18,12 @@
 
 import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
 import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
-import static android.service.notification.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 +37,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 +50,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;
@@ -73,23 +69,6 @@
     static final String MANUAL_DND_MODE_ID = ZenModeConfig.MANUAL_RULE_ID;
     static final String TEMP_NEW_MODE_ID = "temp_new_mode";
 
-    // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
-    private static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALARMS =
-            new ZenPolicy.Builder()
-                    .disallowAllSounds()
-                    .allowAlarms(true)
-                    .allowMedia(true)
-                    .allowPriorityChannels(false)
-                    .build();
-
-    // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
-    private static final ZenPolicy POLICY_INTERRUPTION_FILTER_NONE =
-            new ZenPolicy.Builder()
-                    .disallowAllSounds()
-                    .hideAllVisualEffects()
-                    .allowPriorityChannels(false)
-                    .build();
-
     private static final Comparator<Integer> PRIORITIZED_TYPE_COMPARATOR = new Comparator<>() {
 
         private static final ImmutableList</* @AutomaticZenRule.Type */ Integer>
@@ -176,13 +155,9 @@
     }
 
     static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
-        // Manual rule is owned by the system, so we set it here
-        AutomaticZenRule manualRuleWithPkg = new AutomaticZenRule.Builder(manualRule)
-                .setPackage(PACKAGE_ANDROID)
-                .build();
         return new ZenMode(
                 MANUAL_DND_MODE_ID,
-                manualRuleWithPkg,
+                manualRule,
                 Kind.MANUAL_DND,
                 isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED);
     }
@@ -275,46 +250,49 @@
     }
 
     /**
-     * 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();
+    public ZenIcon.Key getIconKey() {
+        if (isManualDnd()) {
+            return ZenIconKeys.MANUAL_DND;
+        }
+        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());
+            }
+        }
+    }
+
+    /** Returns the interruption filter of the mode. */
+    @NotificationManager.InterruptionFilter
+    public int getInterruptionFilter() {
+        return mRule.getInterruptionFilter();
     }
 
     /**
-     * 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.
+     * Sets the interruption filter of the mode. This is valid for {@link AutomaticZenRule}-backed
+     * modes (and not manual DND).
      */
-    @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 void setInterruptionFilter(@NotificationManager.InterruptionFilter int filter) {
+        if (isManualDnd() || !canEditPolicy()) {
+            throw new IllegalStateException("Cannot update interruption filter for mode " + this);
         }
-
-        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)));
-        }
-
-        return iconLoader.getIcon(context, mRule);
+        mRule.setInterruptionFilter(filter);
     }
 
     @NonNull
@@ -325,10 +303,12 @@
                 return requireNonNull(mRule.getZenPolicy());
 
             case NotificationManager.INTERRUPTION_FILTER_ALARMS:
-                return POLICY_INTERRUPTION_FILTER_ALARMS;
+                return new ZenPolicy.Builder(ZenModeConfig.getDefaultZenPolicy()).build()
+                        .overwrittenWith(ZenPolicy.getBasePolicyInterruptionFilterAlarms());
 
             case NotificationManager.INTERRUPTION_FILTER_NONE:
-                return POLICY_INTERRUPTION_FILTER_NONE;
+                return new ZenPolicy.Builder(ZenModeConfig.getDefaultZenPolicy()).build()
+                        .overwrittenWith(ZenPolicy.getBasePolicyInterruptionFilterNone());
 
             case NotificationManager.INTERRUPTION_FILTER_UNKNOWN:
             default:
@@ -345,6 +325,10 @@
      */
     @SuppressLint("WrongConstant")
     public void setPolicy(@NonNull ZenPolicy policy) {
+        if (!canEditPolicy()) {
+            throw new IllegalStateException("Cannot update ZenPolicy for mode " + this);
+        }
+
         ZenPolicy currentPolicy = getPolicy();
         if (currentPolicy.equals(policy)) {
             return;
@@ -361,6 +345,12 @@
         mRule.setZenPolicy(policy);
     }
 
+    /**
+     * Returns the {@link ZenDeviceEffects} of the mode.
+     *
+     * <p>This is never {@code null}; if the backing AutomaticZenRule doesn't have effects set then
+     * a default (empty) effects set is returned.
+     */
     @NonNull
     public ZenDeviceEffects getDeviceEffects() {
         return mRule.getDeviceEffects() != null
@@ -368,6 +358,15 @@
                 : new ZenDeviceEffects.Builder().build();
     }
 
+    /** Sets the {@link ZenDeviceEffects} of the mode. */
+    public void setDeviceEffects(@NonNull ZenDeviceEffects effects) {
+        checkNotNull(effects);
+        if (!canEditPolicy()) {
+            throw new IllegalStateException("Cannot update device effects for mode " + this);
+        }
+        mRule.setDeviceEffects(effects);
+    }
+
     public void setCustomModeConditionId(Context context, Uri conditionId) {
         checkState(SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName()),
                 "Trying to change condition of non-system-owned rule %s (to %s)",
@@ -410,6 +409,18 @@
         return !isManualDnd();
     }
 
+    /**
+     * Whether the mode has an editable policy. Calling {@link #setPolicy},
+     * {@link #setDeviceEffects}, or {@link #setInterruptionFilter} is not valid for modes with a
+     * read-only policy.
+     */
+    public boolean canEditPolicy() {
+        // Cannot edit the policy of a temporarily active non-PRIORITY DND mode.
+        // Note that it's fine to edit the policy of an *AutomaticZenRule* with non-PRIORITY filter;
+        // the filter will we set to PRIORITY if you do.
+        return !isManualDndWithSpecialFilter();
+    }
+
     public boolean canBeDeleted() {
         return !isManualDnd();
     }
@@ -418,6 +429,12 @@
         return mKind == Kind.MANUAL_DND;
     }
 
+    private boolean isManualDndWithSpecialFilter() {
+        return isManualDnd()
+                && (mRule.getInterruptionFilter() == INTERRUPTION_FILTER_ALARMS
+                || mRule.getInterruptionFilter() == INTERRUPTION_FILTER_NONE);
+    }
+
     /**
      * A <em>custom manual</em> mode is a mode created by the user, and not yet assigned an
      * automatic trigger condition (neither time schedule nor a calendar).
@@ -433,6 +450,18 @@
         return mRule.isEnabled();
     }
 
+    /**
+     * Enables or disables the mode.
+     *
+     * <p>The DND mode cannot be disabled; trying to do so will fail.
+     */
+    public void setEnabled(boolean enabled) {
+        if (isManualDnd()) {
+            throw new IllegalStateException("Cannot update enabled for manual DND mode " + this);
+        }
+        mRule.setEnabled(enabled);
+    }
+
     public boolean isActive() {
         return mStatus == Status.ENABLED_AND_ACTIVE;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
index c8a12f4..71e03c1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
@@ -16,6 +16,11 @@
 
 package com.android.settingslib.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.app.NotificationManager.zenModeToInterruptionFilter;
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AutomaticZenRule;
@@ -112,14 +117,22 @@
 
     private ZenMode getManualDndMode(ZenModeConfig config) {
         ZenModeConfig.ZenRule manualRule = config.manualRule;
+
+        // If DND is currently on with an interruption filter other than PRIORITY, construct the
+        // rule with that. DND will be *non-editable* while in this state.
+        int dndInterruptionFilter = config.isManualActive()
+                ? zenModeToInterruptionFilter(manualRule.zenMode)
+                : INTERRUPTION_FILTER_PRIORITY;
+
         AutomaticZenRule manualDndRule = new AutomaticZenRule.Builder(
-                mContext.getString(R.string.zen_mode_settings_title), manualRule.conditionId)
-                .setType(manualRule.type)
+                mContext.getString(R.string.zen_mode_do_not_disturb_name), manualRule.conditionId)
+                .setPackage(PACKAGE_ANDROID)
+                .setType(AutomaticZenRule.TYPE_OTHER)
                 .setZenPolicy(manualRule.zenPolicy)
                 .setDeviceEffects(manualRule.zenDeviceEffects)
-                .setManualInvocationAllowed(manualRule.allowManualInvocation)
+                .setManualInvocationAllowed(true)
                 .setConfigurationActivity(null) // No further settings
-                .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+                .setInterruptionFilter(dndInterruptionFilter)
                 .build();
 
         return ZenMode.manualDndMode(manualDndRule, config.isManualActive());
@@ -150,7 +163,7 @@
                 durationConditionId = ZenModeConfig.toTimeCondition(mContext,
                         (int) forDuration.toMinutes(), ActivityManager.getCurrentUser(), true).id;
             }
-            mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+            mNotificationManager.setZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                     durationConditionId, TAG, /* fromUser= */ true);
 
         } else {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 6198d80..d71b337 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -181,7 +181,7 @@
      * admin status.
      */
     public Dialog createDialog(Activity activity,
-            ActivityStarter activityStarter, boolean canCreateAdminUser,
+            @NonNull ActivityStarter activityStarter, boolean canCreateAdminUser,
             NewUserData successCallback, Runnable cancelCallback) {
         mActivity = activity;
         mCustomDialogHelper = new CustomDialogHelper(activity);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index 46f2290..c4c4ed8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -31,6 +31,7 @@
 import android.widget.EditText;
 import android.widget.ImageView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
@@ -126,9 +127,11 @@
      * @param activityStarter - ActivityStarter is called with appropriate intents and request
      *                        codes to take photo/choose photo/crop photo.
      */
-    public Dialog createDialog(Activity activity, ActivityStarter activityStarter,
-            @Nullable Drawable oldUserIcon, String defaultUserName,
-            BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
+    public @NonNull Dialog createDialog(@NonNull Activity activity,
+            @NonNull ActivityStarter activityStarter, @Nullable Drawable oldUserIcon,
+            @Nullable String defaultUserName,
+            @Nullable BiConsumer<String, Drawable> successCallback,
+            @Nullable Runnable cancelCallback) {
         LayoutInflater inflater = LayoutInflater.from(activity);
         View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index cdc3f12..f38e91a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.util.UserIcons;
 import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.settingslib.R;
 import com.android.settingslib.utils.ThreadUtils;
 
 import com.google.common.util.concurrent.FutureCallback;
@@ -132,6 +133,13 @@
         intent.addCategory(Intent.CATEGORY_DEFAULT);
         if (Flags.avatarSync()) {
             intent.putExtra(EXTRA_IS_USER_NEW, isUserNew);
+            // Fix vulnerability b/341688848 by explicitly set the class name of avatar picker.
+            if (Flags.fixAvatarCrossUserLeak()) {
+                final String packageName =
+                        mActivity.getString(R.string.config_avatar_picker_package);
+                final String className = mActivity.getString(R.string.config_avatar_picker_class);
+                intent.setClassName(packageName, className);
+            }
         } else {
             // SettingsLib is used by multiple apps therefore we need to know out of all apps
             // using settingsLib which one is the one we return value to.
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/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 33d23a3..48a1af6 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -72,3 +72,10 @@
     dxflags: ["--multi-dex"],
     manifest: "AndroidManifest.xml",
 }
+
+test_module_config {
+    name: "SettingsLibTests_settingslib_users",
+    base: "SettingsLibTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.settingslib.users."],
+}
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/DeviceSettingsProviderServiceStatusTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatusTest.kt
new file mode 100644
index 0000000..aa22fac
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatusTest.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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class DeviceSettingsProviderServiceStatusTest {
+
+    @Test
+    fun parcelOperation() {
+        val item =
+            DeviceSettingsProviderServiceStatus(
+                enabled = true,
+                extras = Bundle().apply { putString("key1", "value1") },
+            )
+
+        val fromParcel = writeAndRead(item)
+
+        assertThat(fromParcel.enabled).isEqualTo(item.enabled)
+        assertThat(fromParcel.extras.getString("key1")).isEqualTo(item.extras.getString("key1"))
+    }
+
+    private fun writeAndRead(
+        item: DeviceSettingsProviderServiceStatus
+    ): DeviceSettingsProviderServiceStatus {
+        val parcel = Parcel.obtain()
+        item.writeToParcel(parcel, 0)
+        parcel.setDataPosition(0)
+        return DeviceSettingsProviderServiceStatus.CREATOR.createFromParcel(parcel)
+    }
+}
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..ce155b5 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,10 +28,12 @@
 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
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsProviderServiceStatus
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
 import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
@@ -46,10 +48,8 @@
 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -58,12 +58,9 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.verify
@@ -84,9 +81,6 @@
     @Mock private lateinit var configService: IDeviceSettingsConfigProviderService.Stub
     @Mock private lateinit var settingProviderService1: IDeviceSettingsProviderService.Stub
     @Mock private lateinit var settingProviderService2: IDeviceSettingsProviderService.Stub
-    @Captor
-    private lateinit var metadataChangeCaptor:
-        ArgumentCaptor<BluetoothAdapter.OnMetadataChangedListener>
 
     private lateinit var underTest: DeviceSettingRepository
     private val testScope = TestScope()
@@ -152,6 +146,12 @@
     fun getDeviceSettingsConfig_withMetadata_success() {
         testScope.runTest {
             `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+            `when`(settingProviderService1.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
+            `when`(settingProviderService2.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
 
             val config = underTest.getDeviceSettingsConfig(cachedDevice)
 
@@ -160,32 +160,40 @@
     }
 
     @Test
-    fun getDeviceSettingsConfig_waitMetadataChange_success() {
+    fun getDeviceSettingsConfig_noMetadata_returnNull() {
+        testScope.runTest {
+            `when`(
+                bluetoothDevice.getMetadata(
+                    DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+                .thenReturn("".toByteArray())
+            `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+            `when`(settingProviderService1.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
+            `when`(settingProviderService2.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
+
+            val config = underTest.getDeviceSettingsConfig(cachedDevice)
+
+            assertThat(config).isNull()
+        }
+    }
+
+    @Test
+    fun getDeviceSettingsConfig_providerServiceNotEnabled_returnNull() {
         testScope.runTest {
             `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
-            `when`(
-                    bluetoothDevice.getMetadata(
-                        DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
-                .thenReturn("".toByteArray())
-
-            var config: DeviceSettingConfigModel? = null
-            val job = launch { config = underTest.getDeviceSettingsConfig(cachedDevice) }
-            delay(1000)
-            verify(bluetoothAdapter)
-                .addOnMetadataChangedListener(
-                    eq(bluetoothDevice), any(), metadataChangeCaptor.capture())
-            metadataChangeCaptor.value.onMetadataChanged(
-                bluetoothDevice,
-                DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
-                BLUETOOTH_DEVICE_METADATA.toByteArray(),
+            `when`(settingProviderService1.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(false)
             )
-            `when`(
-                    bluetoothDevice.getMetadata(
-                        DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
-                .thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
+            `when`(settingProviderService2.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
 
-            job.join()
-            assertConfig(config!!, DEVICE_SETTING_CONFIG)
+            val config = underTest.getDeviceSettingsConfig(cachedDevice)
+
+            assertThat(config).isNull()
         }
     }
 
@@ -211,6 +219,12 @@
                     .getArgument<IDeviceSettingsListener>(1)
                     .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
             }
+            `when`(settingProviderService1.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
+            `when`(settingProviderService2.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
             var setting: DeviceSettingModel? = null
 
             underTest
@@ -233,6 +247,12 @@
                     .getArgument<IDeviceSettingsListener>(1)
                     .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
             }
+            `when`(settingProviderService1.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
+            `when`(settingProviderService2.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
             var setting: DeviceSettingModel? = null
 
             underTest
@@ -246,6 +266,34 @@
     }
 
     @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))
+            }
+            `when`(settingProviderService1.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
+            `when`(settingProviderService2.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
+            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 {
@@ -276,6 +324,12 @@
                     .getArgument<IDeviceSettingsListener>(1)
                     .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
             }
+            `when`(settingProviderService1.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
+            `when`(settingProviderService2.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
             var setting: DeviceSettingModel? = null
 
             underTest
@@ -308,6 +362,12 @@
                     .getArgument<IDeviceSettingsListener>(1)
                     .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
             }
+            `when`(settingProviderService1.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
+            `when`(settingProviderService2.serviceStatus).thenReturn(
+                DeviceSettingsProviderServiceStatus(true)
+            )
             var setting: DeviceSettingModel? = null
 
             underTest
@@ -359,6 +419,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 +484,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 +497,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 +532,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/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 67c73b1..c136644 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -199,6 +199,15 @@
         }
     }
 
+    @EnableFlags(android.app.Flags.FLAG_MODES_UI)
+    @Test
+    fun getModes_returnsModes() {
+        val modesList = listOf(TestModeBuilder().setId("One").build())
+        `when`(zenModesBackend.modes).thenReturn(modesList)
+
+        assertThat(underTest.getModes()).isEqualTo(modesList)
+    }
+
     private fun triggerIntent(action: String, extras: Map<String, Parcelable>? = null) {
         verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any())
         val intent = Intent(action)
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..d08d91d 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,9 +20,11 @@
 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;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
@@ -30,29 +32,22 @@
 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 static org.junit.Assert.assertThrows;
 
 import android.app.AutomaticZenRule;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Parcel;
 import android.service.notification.Condition;
 import android.service.notification.SystemZenRules;
+import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 
 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;
@@ -78,20 +73,26 @@
                     .build();
 
     @Test
-    public void testBasicMethods() {
+    public void testBasicMethods_mode() {
         ZenMode zenMode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, true));
 
         assertThat(zenMode.getId()).isEqualTo("id");
         assertThat(zenMode.getRule()).isEqualTo(ZEN_RULE);
         assertThat(zenMode.isManualDnd()).isFalse();
         assertThat(zenMode.canEditNameAndIcon()).isTrue();
+        assertThat(zenMode.canEditPolicy()).isTrue();
         assertThat(zenMode.canBeDeleted()).isTrue();
         assertThat(zenMode.isActive()).isTrue();
+    }
 
-        ZenMode manualMode = ZenMode.manualDndMode(ZEN_RULE, false);
+    @Test
+    public void testBasicMethods_manualDnd() {
+        ZenMode manualMode = TestModeBuilder.MANUAL_DND_INACTIVE;
+
         assertThat(manualMode.getId()).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
         assertThat(manualMode.isManualDnd()).isTrue();
         assertThat(manualMode.canEditNameAndIcon()).isFalse();
+        assertThat(manualMode.canEditPolicy()).isTrue();
         assertThat(manualMode.canBeDeleted()).isFalse();
         assertThat(manualMode.isActive()).isFalse();
         assertThat(manualMode.getRule().getPackageName()).isEqualTo(PACKAGE_ANDROID);
@@ -212,7 +213,7 @@
         ZenMode zenMode = new ZenMode("id", azr, zenConfigRuleFor(azr, false));
 
         assertThat(zenMode.getPolicy()).isEqualTo(
-                new ZenPolicy.Builder()
+                new ZenPolicy.Builder(ZenModeConfig.getDefaultZenPolicy())
                         .disallowAllSounds()
                         .allowAlarms(true)
                         .allowMedia(true)
@@ -229,9 +230,8 @@
         ZenMode zenMode = new ZenMode("id", azr, zenConfigRuleFor(azr, false));
 
         assertThat(zenMode.getPolicy()).isEqualTo(
-                new ZenPolicy.Builder()
+                new ZenPolicy.Builder(ZenModeConfig.getDefaultZenPolicy())
                         .disallowAllSounds()
-                        .hideAllVisualEffects()
                         .allowPriorityChannels(false)
                         .build());
     }
@@ -252,6 +252,77 @@
     }
 
     @Test
+    public void getInterruptionFilter_returnsFilter() {
+        ZenMode mode = new TestModeBuilder().setInterruptionFilter(
+                INTERRUPTION_FILTER_ALARMS).build();
+
+        assertThat(mode.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+    }
+
+    @Test
+    public void setInterruptionFilter_setsFilter() {
+        ZenMode mode = new TestModeBuilder().setInterruptionFilter(
+                INTERRUPTION_FILTER_ALARMS).build();
+
+        mode.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
+
+        assertThat(mode.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
+    }
+
+    @Test
+    public void setInterruptionFilter_manualDnd_throws() {
+        ZenMode manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE;
+
+        assertThrows(IllegalStateException.class,
+                () -> manualDnd.setInterruptionFilter(INTERRUPTION_FILTER_ALL));
+    }
+
+    @Test
+    public void canEditPolicy_onlyFalseForSpecialDnd() {
+        assertThat(TestModeBuilder.EXAMPLE.canEditPolicy()).isTrue();
+        assertThat(TestModeBuilder.MANUAL_DND_ACTIVE.canEditPolicy()).isTrue();
+        assertThat(TestModeBuilder.MANUAL_DND_INACTIVE.canEditPolicy()).isTrue();
+
+        ZenMode dndWithAlarms = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_ALARMS, true);
+        assertThat(dndWithAlarms.canEditPolicy()).isFalse();
+        ZenMode dndWithNone = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, true);
+        assertThat(dndWithNone.canEditPolicy()).isFalse();
+
+        // Note: Backend will never return an inactive manual mode with custom filter.
+        ZenMode badDndWithAlarms = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_ALARMS, false);
+        assertThat(badDndWithAlarms.canEditPolicy()).isFalse();
+        ZenMode badDndWithNone = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, false);
+        assertThat(badDndWithNone.canEditPolicy()).isFalse();
+    }
+
+    @Test
+    public void canEditPolicy_whenTrue_allowsSettingPolicyAndEffects() {
+        ZenMode normalDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_PRIORITY, true);
+
+        assertThat(normalDnd.canEditPolicy()).isTrue();
+
+        ZenPolicy somePolicy = new ZenPolicy.Builder().showBadges(true).build();
+        normalDnd.setPolicy(somePolicy);
+        assertThat(normalDnd.getPolicy()).isEqualTo(somePolicy);
+
+        ZenDeviceEffects someEffects = new ZenDeviceEffects.Builder()
+                .setShouldUseNightMode(true).build();
+        normalDnd.setDeviceEffects(someEffects);
+        assertThat(normalDnd.getDeviceEffects()).isEqualTo(someEffects);
+    }
+
+    @Test
+    public void canEditPolicy_whenFalse_preventsSettingFilterPolicyOrEffects() {
+        ZenMode specialDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_ALARMS, true);
+
+        assertThat(specialDnd.canEditPolicy()).isFalse();
+        assertThrows(IllegalStateException.class,
+                () -> specialDnd.setPolicy(ZEN_POLICY));
+        assertThrows(IllegalStateException.class,
+                () -> specialDnd.setDeviceEffects(new ZenDeviceEffects.Builder().build()));
+    }
+
+    @Test
     public void comparator_prioritizes() {
         ZenMode manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE;
         ZenMode driving1 = new TestModeBuilder().setName("b1").setType(TYPE_DRIVING).build();
@@ -289,72 +360,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_isDndIcon() {
+        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_special_dnd);
     }
 
     private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java
index 539519b..aae72b3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java
@@ -16,12 +16,14 @@
 
 package com.android.settingslib.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 import static android.service.notification.Condition.SOURCE_UNKNOWN;
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.ZenAdapters.notificationPolicyToZenPolicy;
 import static android.service.notification.ZenPolicy.STATE_ALLOW;
 
@@ -47,8 +49,6 @@
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 
-import com.android.settingslib.R;
-
 import com.google.common.collect.ImmutableMap;
 
 import org.junit.Before;
@@ -172,9 +172,8 @@
         // all modes exist, but none of them are currently active
         assertThat(modes).containsExactly(
                         ZenMode.manualDndMode(
-                                new AutomaticZenRule.Builder(
-                                        mContext.getString(R.string.zen_mode_settings_title),
-                                        Uri.EMPTY)
+                                new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
+                                        .setPackage(PACKAGE_ANDROID)
                                         .setType(AutomaticZenRule.TYPE_OTHER)
                                         .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                                         .setZenPolicy(notificationPolicyToZenPolicy(dndPolicy))
@@ -196,15 +195,59 @@
 
         ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
 
+        assertThat(mode).isNotNull();
         assertThat(mode).isEqualTo(
                 ZenMode.manualDndMode(
-                        new AutomaticZenRule.Builder(
-                                mContext.getString(R.string.zen_mode_settings_title), Uri.EMPTY)
+                        new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
+                                .setPackage(PACKAGE_ANDROID)
                                 .setType(AutomaticZenRule.TYPE_OTHER)
                                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                                 .setZenPolicy(notificationPolicyToZenPolicy(dndPolicy))
                                 .setManualInvocationAllowed(true)
                                 .build(), false));
+
+        assertThat(mode.isManualDnd()).isTrue();
+        assertThat(mode.isEnabled()).isTrue();
+        assertThat(mode.isActive()).isFalse();
+        assertThat(mode.canEditPolicy()).isTrue();
+        assertThat(mode.getPolicy()).isEqualTo(notificationPolicyToZenPolicy(dndPolicy));
+    }
+
+    @Test
+    public void getMode_dndWithOtherInterruptionFilter_returnsSpecialDndMode() {
+        ZenModeConfig config = configWithManualRule(new ZenModeConfig(), true);
+        config.manualRule.zenMode = Settings.Global.ZEN_MODE_ALARMS;
+        Policy dndPolicyForPriority = new Policy(Policy.PRIORITY_CATEGORY_ALARMS,
+                Policy.PRIORITY_SENDERS_CONTACTS, Policy.PRIORITY_SENDERS_CONTACTS);
+        config.applyNotificationPolicy(dndPolicyForPriority);
+        when(mNm.getZenModeConfig()).thenReturn(config);
+
+        ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
+
+        assertThat(mode).isNotNull();
+        assertThat(mode).isEqualTo(
+                ZenMode.manualDndMode(
+                        new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
+                                .setPackage(PACKAGE_ANDROID)
+                                .setType(AutomaticZenRule.TYPE_OTHER)
+                                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                                .setZenPolicy(notificationPolicyToZenPolicy(dndPolicyForPriority))
+                                .setManualInvocationAllowed(true)
+                                .build(), true));
+
+        assertThat(mode.isManualDnd()).isTrue();
+        assertThat(mode.isEnabled()).isTrue();
+        assertThat(mode.isActive()).isTrue();
+
+        // Mode itself has a special fixed policy, different to the rule.
+        assertThat(mode.canEditPolicy()).isFalse();
+        assertThat(mode.getPolicy()).isEqualTo(
+                new ZenPolicy.Builder(ZenModeConfig.getDefaultZenPolicy())
+                        .disallowAllSounds()
+                        .allowAlarms(true)
+                        .allowMedia(true)
+                        .allowPriorityChannels(false)
+                        .build());
     }
 
     @Test
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/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index ec50323..e85ba45 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2173,8 +2173,7 @@
                                             (1 << AudioManager.STREAM_NOTIFICATION) |
                                             (1 << AudioManager.STREAM_SYSTEM) |
                                             (1 << AudioManager.STREAM_SYSTEM_ENFORCED);
-            if (!mContext.getResources().getBoolean(
-                    com.android.internal.R.bool.config_voice_capable)) {
+            if (!getTelephonyManager().isVoiceCapable()) {
                 ringerModeAffectedStreams |= (1 << AudioManager.STREAM_MUSIC);
             }
             loadSetting(stmt, Settings.System.MODE_RINGER_STREAMS_AFFECTED,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 121bd3e..bfbf41d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -99,6 +99,12 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("SyncDisabledForTests: ");
+        MyShellCommand.getSyncDisabledForTests(pw, pw);
+
+        pw.print("Is mainline: ");
+        pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService());
+
         final IContentProvider iprovider = mProvider.getIContentProvider();
         pw.println("DeviceConfig flags:");
         for (String line : MyShellCommand.listAll(iprovider)) {
@@ -232,6 +238,17 @@
             return Binder.getCallingUid() == Process.ROOT_UID;
         }
 
+        private static int getSyncDisabledForTests(PrintWriter pOut, PrintWriter pErr) {
+            int syncDisabledModeInt = DeviceConfig.getSyncDisabledMode();
+            String syncDisabledModeString = formatSyncDisabledMode(syncDisabledModeInt);
+            if (syncDisabledModeString == null) {
+                pErr.println("Unknown mode: " + syncDisabledModeInt);
+                return -1;
+            }
+            pOut.println(syncDisabledModeString);
+            return 0;
+        }
+
       public static HashMap<String, String> getAllFlags(IContentProvider provider) {
         HashMap<String, String> allFlags = new HashMap<String, String>();
         for (DeviceConfig.Properties properties : DeviceConfig.getAllProperties()) {
@@ -597,14 +614,7 @@
                     DeviceConfig.setSyncDisabledMode(syncDisabledModeArg);
                     break;
                 case GET_SYNC_DISABLED_FOR_TESTS:
-                    int syncDisabledModeInt = DeviceConfig.getSyncDisabledMode();
-                    String syncDisabledModeString = formatSyncDisabledMode(syncDisabledModeInt);
-                    if (syncDisabledModeString == null) {
-                        perr.println("Unknown mode: " + syncDisabledModeInt);
-                        return -1;
-                    }
-                    pout.println(syncDisabledModeString);
-                    break;
+                    return getSyncDisabledForTests(pout, perr);
                 default:
                     perr.println("Unspecified command");
                     return -1;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index 006e644..62401a1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -81,3 +81,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "sync_local_overrides_removal_new_storage"
+    namespace: "core_experiments_team_internal"
+    description: "When DeviceConfig overrides are deleted, delete new storage overrides too."
+    bug: "361643653"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
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 2e98c1f..157af7d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -92,6 +92,7 @@
     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
     <uses-permission android:name="android.permission.MASTER_CLEAR" />
     <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" />
     <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" />
     <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" />
     <uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" />
@@ -370,6 +371,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" />
 
@@ -484,6 +488,7 @@
 
         <activity android:name=".touchpad.tutorial.ui.view.TouchpadTutorialActivity"
             android:exported="true"
+            android:showForAllUsers="true"
             android:screenOrientation="userLandscape"
             android:theme="@style/Theme.AppCompat.NoActionBar">
             <intent-filter>
@@ -494,6 +499,7 @@
 
         <activity android:name=".inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity"
             android:exported="true"
+            android:showForAllUsers="true"
             android:screenOrientation="userLandscape"
             android:theme="@style/Theme.AppCompat.NoActionBar">
             <intent-filter>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index c60eb61..fb1f715 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -41,6 +41,7 @@
         "SettingsLibDisplayUtils",
         "SettingsLibSettingsTheme",
         "com_android_a11y_menu_flags_lib",
+        "//frameworks/libs/systemui:view_capture",
     ],
 
     optimize: {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
index fd9a9c6..3c73eca 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
@@ -3,8 +3,7 @@
     android:id="@+id/shortcutItem"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingTop="@dimen/grid_item_padding"
-    android:paddingBottom="@dimen/grid_item_padding"
+    android:padding="@dimen/grid_item_padding"
     android:gravity="center">
 
   <ImageButton
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml
index 6be7655..adaa655 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/paged_menu.xml
@@ -1,25 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/row_width"
     android:layout_height="match_parent"
-    android:id="@+id/coordinatorLayout"
-    android:background="@drawable/view_background"
-    >
-  <LinearLayout
+    android:orientation="vertical"
+    android:background="@drawable/view_background">
+
+  <androidx.viewpager2.widget.ViewPager2
+      android:id="@+id/view_pager"
       android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:orientation="vertical">
+      android:layout_height="0dp"
+      android:layout_weight="1"
+      android:paddingTop="@dimen/table_margin_top"
+      android:paddingBottom="@dimen/a11ymenu_layout_margin"
+      android:gravity="center"
+      />
 
-    <androidx.viewpager2.widget.ViewPager2
-        android:id="@+id/view_pager"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingTop="@dimen/table_margin_top"
-        android:paddingBottom="@dimen/a11ymenu_layout_margin"
-        android:layout_gravity="center"
-        android:gravity="center"
-        />
-
-    <include layout="@layout/footerlayout_switch_page"/>
-  </LinearLayout>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+  <include layout="@layout/footerlayout_switch_page"/>
+</LinearLayout>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index c2cf6e1..c333a7a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.accessibility.accessibilitymenu.view;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.view.LayoutInflater;
 import android.view.TouchDelegate;
@@ -43,16 +42,14 @@
     private final int mLargeTextSize;
 
     private final AccessibilityMenuService mService;
-    private final LayoutInflater mInflater;
     private final List<A11yMenuShortcut> mShortcutDataList;
     private final ShortcutDrawableUtils mShortcutDrawableUtils;
 
     public A11yMenuAdapter(
             AccessibilityMenuService service,
-            Context displayContext, List<A11yMenuShortcut> shortcutDataList) {
+            List<A11yMenuShortcut> shortcutDataList) {
         this.mService = service;
         this.mShortcutDataList = shortcutDataList;
-        mInflater = LayoutInflater.from(displayContext);
 
         mShortcutDrawableUtils = new ShortcutDrawableUtils(service);
 
@@ -78,7 +75,8 @@
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
         if (convertView == null) {
-            convertView = mInflater.inflate(R.layout.grid_item, parent, false);
+            convertView = LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.grid_item, parent, false);
 
             configureShortcutSize(convertView,
                     A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java
index 78fbf01..136a4ed 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuFooter.java
@@ -55,6 +55,8 @@
     private View mBottomListDivider;
     private final A11yMenuFooterCallBack mCallBack;
     private final ViewGroup mMenuLayout;
+    private ViewGroup mFooterContainer;
+    private int mFooterContainerBaseHeight = 0;
     private int mRightToLeftDirection = LAYOUT_DIRECTION_LTR;
 
     public A11yMenuFooter(ViewGroup menuLayout, A11yMenuFooterCallBack callBack) {
@@ -74,6 +76,15 @@
                 ? mPageRightBtn : mPageLeftBtn;
     }
 
+    void adjustFooterToDensityScale(float densityScale) {
+        mFooterContainer.getLayoutParams().height =
+                (int) (mFooterContainerBaseHeight / densityScale);
+    }
+
+    int getHeight() {
+        return mFooterContainer.getLayoutParams().height;
+    }
+
     /** Sets right to left direction of footer. */
     public void updateRightToLeftDirection(Configuration configuration) {
         mRightToLeftDirection = TextUtils.getLayoutDirectionFromLocale(
@@ -85,8 +96,9 @@
     }
 
     private void configureFooterLayout(ViewGroup menuLayout) {
-        ViewGroup footerContainer = menuLayout.findViewById(R.id.footerlayout);
-        footerContainer.setVisibility(View.VISIBLE);
+        mFooterContainer = menuLayout.findViewById(R.id.footerlayout);
+        mFooterContainer.setVisibility(View.VISIBLE);
+        mFooterContainerBaseHeight = mFooterContainer.getLayoutParams().height;
 
         mPageLeftBtn = menuLayout.findViewById(R.id.menu_left_button);
         mPageRightBtn = menuLayout.findViewById(R.id.menu_right_button);
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index de3c472..3db61a5 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -22,6 +22,8 @@
 import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 
+import static com.android.app.viewcapture.ViewCaptureFactory.getViewCaptureAwareWindowManagerInstance;
+
 import static java.lang.Math.max;
 
 import android.animation.Animator;
@@ -51,7 +53,9 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.UiContext;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
 import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.R;
@@ -101,7 +105,6 @@
     };
 
     private final AccessibilityMenuService mService;
-    private final WindowManager mWindowManager;
     private final DisplayManager mDisplayManager;
     private ViewGroup mLayout;
     private WindowManager.LayoutParams mLayoutParameter;
@@ -111,7 +114,6 @@
 
     public A11yMenuOverlayLayout(AccessibilityMenuService service) {
         mService = service;
-        mWindowManager = mService.getSystemService(WindowManager.class);
         mDisplayManager = mService.getSystemService(DisplayManager.class);
         configureLayout();
         mHandler = new Handler(Looper.getMainLooper());
@@ -134,8 +136,7 @@
         int lastVisibilityState = View.GONE;
         if (mLayout != null) {
             lastVisibilityState = mLayout.getVisibility();
-            mWindowManager.removeView(mLayout);
-            mLayout = null;
+            clearLayout();
         }
 
         if (mLayoutParameter == null) {
@@ -143,14 +144,17 @@
         }
 
         final Display display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
-        final Context context = mService.createDisplayContext(display).createWindowContext(
-                TYPE_ACCESSIBILITY_OVERLAY, null);
-        mLayout = new A11yMenuFrameLayout(context);
-        updateLayoutPosition();
-        inflateLayoutAndSetOnTouchListener(mLayout, context);
-        mA11yMenuViewPager = new A11yMenuViewPager(mService, context);
+        final Context uiContext = mService.createWindowContext(
+                display, TYPE_ACCESSIBILITY_OVERLAY, /* options= */null);
+        final ViewCaptureAwareWindowManager windowManager =
+                getViewCaptureAwareWindowManagerInstance(uiContext,
+                        com.android.systemui.Flags.enableViewCaptureTracing());
+        mLayout = new A11yMenuFrameLayout(uiContext);
+        updateLayoutPosition(uiContext);
+        inflateLayoutAndSetOnTouchListener(mLayout, uiContext);
+        mA11yMenuViewPager = new A11yMenuViewPager(mService);
         mA11yMenuViewPager.configureViewPagerAndFooter(mLayout, createShortcutList(), pageIndex);
-        mWindowManager.addView(mLayout, mLayoutParameter);
+        windowManager.addView(mLayout, mLayoutParameter);
         mLayout.setVisibility(lastVisibilityState);
         mA11yMenuViewPager.updateFooterState();
 
@@ -159,7 +163,11 @@
 
     public void clearLayout() {
         if (mLayout != null) {
-            mWindowManager.removeView(mLayout);
+            ViewCaptureAwareWindowManager windowManager = getViewCaptureAwareWindowManagerInstance(
+                    mLayout.getContext(), com.android.systemui.Flags.enableViewCaptureTracing());
+            if (windowManager != null) {
+                windowManager.removeView(mLayout);
+            }
             mLayout.setOnTouchListener(null);
             mLayout = null;
         }
@@ -170,8 +178,11 @@
         if (mLayout == null || mLayoutParameter == null) {
             return;
         }
-        updateLayoutPosition();
-        mWindowManager.updateViewLayout(mLayout, mLayoutParameter);
+        updateLayoutPosition(mLayout.getContext());
+        WindowManager windowManager = mLayout.getContext().getSystemService(WindowManager.class);
+        if (windowManager != null) {
+            windowManager.updateViewLayout(mLayout, mLayoutParameter);
+        }
     }
 
     private void initLayoutParams() {
@@ -183,8 +194,8 @@
         mLayoutParameter.setTitle(mService.getString(R.string.accessibility_menu_service_name));
     }
 
-    private void inflateLayoutAndSetOnTouchListener(ViewGroup view, Context displayContext) {
-        LayoutInflater inflater = LayoutInflater.from(displayContext);
+    private void inflateLayoutAndSetOnTouchListener(ViewGroup view, @UiContext Context uiContext) {
+        LayoutInflater inflater = LayoutInflater.from(uiContext);
         inflater.inflate(R.layout.paged_menu, view);
         view.setOnTouchListener(mService);
     }
@@ -238,7 +249,11 @@
     }
 
     /** Updates a11y menu layout position by configuring layout params. */
-    private void updateLayoutPosition() {
+    private void updateLayoutPosition(@UiContext @NonNull Context uiContext) {
+        WindowManager windowManager = uiContext.getSystemService(WindowManager.class);
+        if (windowManager == null) {
+            return;
+        }
         final Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
         final Configuration configuration = mService.getResources().getConfiguration();
         final int orientation = configuration.orientation;
@@ -276,14 +291,13 @@
             mLayoutParameter.height = WindowManager.LayoutParams.WRAP_CONTENT;
             mLayout.setBackgroundResource(R.drawable.shadow_0deg);
         }
-
         // Adjusts the y position of a11y menu layout to make the layout not to overlap bottom
         // navigation bar window.
-        updateLayoutByWindowInsetsIfNeeded();
+        updateLayoutByWindowInsetsIfNeeded(windowManager);
         mLayout.setOnApplyWindowInsetsListener(
                 (view, insets) -> {
-                    if (updateLayoutByWindowInsetsIfNeeded()) {
-                        mWindowManager.updateViewLayout(mLayout, mLayoutParameter);
+                    if (updateLayoutByWindowInsetsIfNeeded(windowManager)) {
+                        windowManager.updateViewLayout(mLayout, mLayoutParameter);
                     }
                     return view.onApplyWindowInsets(insets);
                 });
@@ -295,9 +309,9 @@
      * This method adjusts the layout position and size to
      * make a11y menu not to overlap navigation bar window.
      */
-    private boolean updateLayoutByWindowInsetsIfNeeded() {
+    private boolean updateLayoutByWindowInsetsIfNeeded(@NonNull WindowManager windowManager) {
         boolean shouldUpdateLayout = false;
-        WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+        WindowMetrics windowMetrics = windowManager.getCurrentWindowMetrics();
         Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
                 WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
         int xOffset = max(windowInsets.left, windowInsets.right);
@@ -396,7 +410,7 @@
     }
 
     private class A11yMenuFrameLayout extends FrameLayout {
-        A11yMenuFrameLayout(@NonNull Context context) {
+        A11yMenuFrameLayout(@UiContext @NonNull Context context) {
             super(context);
         }
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
index 08bbf19..35f1248 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
@@ -28,6 +28,7 @@
 import android.view.WindowMetrics;
 import android.widget.GridView;
 
+import androidx.recyclerview.widget.RecyclerView;
 import androidx.viewpager2.widget.ViewPager2;
 
 import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
@@ -146,12 +147,8 @@
     /** The container layout for a11y menu. */
     private ViewGroup mA11yMenuLayout;
 
-    /** Display context for inflating views. */
-    private Context mDisplayContext;
-
-    public A11yMenuViewPager(AccessibilityMenuService service, Context displayContext) {
+    public A11yMenuViewPager(AccessibilityMenuService service) {
         this.mService = service;
-        this.mDisplayContext = displayContext;
     }
 
     /**
@@ -167,7 +164,9 @@
         mA11yMenuShortcutList = shortcutDataList;
         initViewPager();
         initChildPage();
-        mA11yMenuFooter = new A11yMenuFooter(a11yMenuLayout, mFooterCallbacks);
+        if (mA11yMenuFooter == null) {
+            mA11yMenuFooter = new A11yMenuFooter(a11yMenuLayout, mFooterCallbacks);
+        }
         mA11yMenuFooter.updateRightToLeftDirection(
                 a11yMenuLayout.getResources().getConfiguration());
         updateFooterState();
@@ -237,11 +236,17 @@
                                     return;
                                 }
 
-                                if (mGridPageList.isEmpty()) {
+                                if (mViewPagerAdapter.getItemCount() == 0) {
                                     return;
                                 }
 
-                                GridView firstGridView = mGridPageList.get(0);
+                                RecyclerView.ViewHolder viewHolder =
+                                        ((RecyclerView) mViewPager.getChildAt(0))
+                                                .findViewHolderForAdapterPosition(0);
+                                if (viewHolder == null) {
+                                    return;
+                                }
+                                GridView firstGridView = (GridView) viewHolder.itemView;
                                 if (firstGridView == null
                                         || firstGridView.getChildAt(0) == null) {
                                     return;
@@ -284,31 +289,28 @@
             DisplayMetrics displayMetrics = mService.getResources().getDisplayMetrics();
             float densityScale = (float) displayMetrics.densityDpi
                     / DisplayMetrics.DENSITY_DEVICE_STABLE;
-            View footerLayout = mA11yMenuLayout.findViewById(R.id.footerlayout);
             // Keeps footer window height unchanged no matter the density is changed.
-            footerLayout.getLayoutParams().height =
-                    (int) (footerLayout.getLayoutParams().height / densityScale);
+            mA11yMenuFooter.adjustFooterToDensityScale(densityScale);
             // Adjust the view pager height for system bar and display cutout insets.
-            WindowManager windowManager = mService.getSystemService(WindowManager.class);
+            WindowManager windowManager = mA11yMenuLayout.getContext()
+                    .getSystemService(WindowManager.class);
             WindowMetrics windowMetric = windowManager.getCurrentWindowMetrics();
             Insets windowInsets = windowMetric.getWindowInsets().getInsetsIgnoringVisibility(
                     WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
             viewPagerHeight =
                     windowMetric.getBounds().height()
-                            - footerLayout.getLayoutParams().height
+                            - mA11yMenuFooter.getHeight()
                             - windowInsets.bottom;
             // Sets vertical interval between grid items.
             int interval =
                     (viewPagerHeight - topMargin - defaultMargin
                             - (rowsInGridView * gridItemHeight))
                             / (rowsInGridView + 1);
-            for (GridView gridView : mGridPageList) {
-                gridView.setVerticalSpacing(interval);
-            }
+            mViewPagerAdapter.setVerticalSpacing(interval);
 
             // Sets padding to view pager.
             final int finalMarginTop = interval + topMargin;
-            mViewPager.setPadding(defaultMargin, finalMarginTop, defaultMargin, defaultMargin);
+            mViewPager.setPadding(0, finalMarginTop, 0, defaultMargin);
         }
         final ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams();
         layoutParams.height = viewPagerHeight;
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java
index 43ec956..4b14e51 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/ViewPagerAdapter.java
@@ -36,11 +36,19 @@
     /** List of shortcuts, split into sub lists per page */
     private List<List<A11yMenuShortcut>> mShortcutList;
     private final AccessibilityMenuService mService;
+    private int mVerticalSpacing = 0;
 
     ViewPagerAdapter(AccessibilityMenuService service) {
         mService = service;
     }
 
+    public void setVerticalSpacing(int spacing) {
+        if (mVerticalSpacing != spacing) {
+            mVerticalSpacing = spacing;
+            notifyDataSetChanged();
+        }
+    }
+
     public void set(List<List<A11yMenuShortcut>> tList) {
         mShortcutList = tList;
         notifyDataSetChanged();
@@ -57,10 +65,11 @@
     @Override
     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
         A11yMenuAdapter adapter = new A11yMenuAdapter(
-                mService, holder.itemView.getContext(), mShortcutList.get(position));
+                mService, mShortcutList.get(position));
         GridView gridView = (GridView) holder.itemView;
         gridView.setNumColumns(A11yMenuViewPager.GridViewParams.getGridColumnCount(mService));
         gridView.setAdapter(adapter);
+        gridView.setVerticalSpacing(mVerticalSpacing);
     }
 
     @Override
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 7c0db8d..be748da 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"
@@ -541,6 +559,16 @@
 }
 
 flag {
+    name: "clipboard_shared_transitions"
+    namespace: "systemui"
+    description: "Show shared transitions from clipboard"
+    bug: "360843770"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "clipboard_image_timeout"
     namespace: "systemui"
     description: "Wait for clipboard image to load before showing UI"
@@ -578,16 +606,6 @@
 }
 
 flag {
-    name: "screenshot_private_profile_behavior_fix"
-    namespace: "systemui"
-    description: "Private profile support for screenshots"
-    bug: "327613051"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "screenshot_save_image_exporter"
     namespace: "systemui"
     description: "Save all screenshots using ImageExporter"
@@ -619,10 +637,13 @@
 }
 
 flag {
-   name: "smartspace_remoteviews_rendering"
+   name: "smartspace_remoteviews_rendering_fix"
    namespace: "systemui"
    description: "Indicate Smartspace RemoteViews rendering"
    bug: "326292691"
+   metadata {
+     purpose: PURPOSE_BUGFIX
+   }
 }
 
 flag {
@@ -633,6 +654,13 @@
 }
 
 flag {
+    name: "smartspace_viewpager2"
+    namespace: "systemui"
+    description: "Use viewpager2 in Smartspace"
+    bug: "259566300"
+}
+
+flag {
    name: "pin_input_field_styled_focus_state"
    namespace: "systemui"
    description: "Enables styled focus states on pin input field if keyboard is connected"
@@ -1022,6 +1050,16 @@
 }
 
 flag {
+  name: "communal_edit_widgets_activity_finish_fix"
+  namespace: "systemui"
+  description: "finish edit widgets activity when stopping"
+  bug: "354725145"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "app_clips_backlinks"
   namespace: "systemui"
   description: "Enables Backlinks improvement feature in App Clips"
@@ -1073,6 +1111,13 @@
 }
 
 flag {
+  name: "media_controls_posts_optimization"
+  namespace: "systemui"
+  description: "Ignore duplicate media notifications posted"
+  bug: "358645640"
+}
+
+flag {
   namespace: "systemui"
   name: "enable_view_capture_tracing"
   description: "Enables view capture tracing in System UI."
@@ -1093,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"
@@ -1331,13 +1366,6 @@
 }
 
 flag {
-    name: "new_picker_ui"
-    namespace: "systemui"
-    description: "Enables the BC25 design of the customization picker UI."
-    bug: "339081035"
-}
-
-flag {
   namespace: "systemui"
   name: "settings_ext_register_content_observer_on_bg_thread"
   description: "Register content observer in callback flow APIs on background thread in SettingsProxyExt."
@@ -1346,3 +1374,34 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "notify_password_text_view_user_activity_in_background"
+    namespace: "systemui"
+    description: "Decide whether to notify the user activity in password text view, to power manager in the background thread."
+    bug: "346882515"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "face_message_defer_update"
+    namespace: "systemui"
+    description: "Only analyze the last n frames when determining whether to defer a face auth help message like low light"
+    bug: "351863611"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "media_load_metadata_via_media_data_loader"
+    namespace: "systemui"
+    description: "Use MediaDataLoader for loading media metadata with better threading"
+    bug: "358350077"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/SystemUI/animation/build.gradle b/packages/SystemUI/animation/build.gradle
index 939455f..16ba42f 100644
--- a/packages/SystemUI/animation/build.gradle
+++ b/packages/SystemUI/animation/build.gradle
@@ -10,13 +10,6 @@
         }
     }
 
-    compileSdk 33
-
-    defaultConfig {
-        minSdk 33
-        targetSdk 33
-    }
-
     lintOptions {
         abortOnError false
     }
@@ -24,10 +17,6 @@
     tasks.withType(JavaCompile) {
         options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
     }
-    kotlinOptions {
-        jvmTarget = '1.8'
-        freeCompilerArgs = ["-Xjvm-default=all"]
-    }
 }
 
 dependencies {
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
index 2b1268e..5b368df 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/BouncerSceneModule.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.scene
 
 import com.android.systemui.bouncer.ui.composable.BouncerScene
-import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.ui.composable.Scene
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/CommunalSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/CommunalSceneModule.kt
index 94b5db2..74ce4bb 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/CommunalSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/CommunalSceneModule.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.scene
 
 import com.android.systemui.communal.ui.compose.CommunalScene
-import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.ui.composable.Scene
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/GoneSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/GoneSceneModule.kt
index bc3fef1..871ade9 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/GoneSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/GoneSceneModule.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.scene
 
-import com.android.systemui.scene.shared.model.Scene
 import com.android.systemui.scene.ui.composable.GoneScene
+import com.android.systemui.scene.ui.composable.Scene
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index 72965fb..bfeaf92 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.keyguard.ui.composable.LockscreenSceneBlueprintModule
 import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.ui.composable.Scene
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeOverlayModule.kt
similarity index 61%
rename from packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt
rename to packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeOverlayModule.kt
index 7e09a10..e55520a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeOverlayModule.kt
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard
+package com.android.systemui.scene
 
-import com.android.systemui.Flags
+import com.android.systemui.notifications.ui.composable.NotificationsShadeOverlay
+import com.android.systemui.scene.ui.composable.Overlay
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
 
-/** Helper for reading or using the new picker UI flag. */
-@Suppress("NOTHING_TO_INLINE")
-object NewPickerUiKeyguardPreview {
+@Module
+interface NotificationsShadeOverlayModule {
 
-    /** Is the new picker UI enabled */
-    @JvmStatic
-    inline val isEnabled
-        get() = Flags.newPickerUi()
+    @Binds @IntoSet fun notificationsShade(overlay: NotificationsShadeOverlay): Overlay
 }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt
index 9b736b8..c58df35 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.scene
 
 import com.android.systemui.notifications.ui.composable.NotificationsShadeScene
-import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.ui.composable.Scene
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsSceneModule.kt
index ee1f525..d55210d 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsSceneModule.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.scene
 
 import com.android.systemui.qs.ui.composable.QuickSettingsScene
-import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.ui.composable.Scene
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeOverlayModule.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeOverlayModule.kt
index d60f14c..bc4adf9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeOverlayModule.kt
@@ -14,10 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume
+package com.android.systemui.scene
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.ui.composable.QuickSettingsShadeOverlay
+import com.android.systemui.scene.ui.composable.Overlay
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+@Module
+interface QuickSettingsShadeOverlayModule {
+
+    @Binds @IntoSet fun quickSettingsShade(overlay: QuickSettingsShadeOverlay): Overlay
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt
index 3d7401d..5bb6ae4 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.scene
 
 import com.android.systemui.qs.ui.composable.QuickSettingsShadeScene
-import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.ui.composable.Scene
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ShadeSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ShadeSceneModule.kt
index c655d6b..186914f 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ShadeSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ShadeSceneModule.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.scene
 
-import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.ui.composable.Scene
 import com.android.systemui.shade.ui.composable.ShadeScene
 import dagger.Binds
 import dagger.Module
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index d164eab..34eafde 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -102,6 +102,7 @@
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.common.shared.model.Text.Companion.loadText
 import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.fold.ui.composable.foldPosture
 import com.android.systemui.fold.ui.helper.FoldPosture
 import com.android.systemui.res.R
@@ -259,8 +260,11 @@
                         viewModel = viewModel.message,
                         modifier = Modifier.align(Alignment.TopCenter),
                     )
-
-                    OutputArea(viewModel = viewModel, modifier = Modifier.align(Alignment.Center))
+                    OutputArea(
+                        viewModel = viewModel,
+                        modifier =
+                            Modifier.align(Alignment.Center).sysuiResTag("bouncer_text_entry")
+                    )
 
                     ActionArea(
                         viewModel = viewModel,
@@ -310,8 +314,11 @@
                         StatusMessage(
                             viewModel = viewModel.message,
                         )
-
-                        OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
+                        OutputArea(
+                            viewModel = viewModel,
+                            modifier =
+                                Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry")
+                        )
                     }
                 }
                 else -> Unit
@@ -417,10 +424,9 @@
                     StatusMessage(
                         viewModel = viewModel.message,
                     )
-
                     OutputArea(
                         viewModel = viewModel,
-                        modifier = Modifier.padding(top = 24.dp).testTag("OutputArea")
+                        modifier = Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry")
                     )
                 }
             },
@@ -485,7 +491,6 @@
                 StatusMessage(
                     viewModel = viewModel.message,
                 )
-
                 OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
 
                 InputArea(
@@ -528,7 +533,7 @@
     // Update state whenever currentSceneKey has changed.
     LaunchedEffect(state, currentSceneKey) {
         if (currentSceneKey != state.transitionState.currentScene) {
-            state.setTargetScene(currentSceneKey, coroutineScope = this)
+            state.setTargetScene(currentSceneKey, animationScope = this)
         }
     }
 
@@ -654,17 +659,16 @@
 ) {
     val authMethodViewModel: AuthMethodBouncerViewModel? by
         viewModel.authMethodViewModel.collectAsStateWithLifecycle()
-
     when (val nonNullViewModel = authMethodViewModel) {
         is PinBouncerViewModel ->
             PinInputDisplay(
                 viewModel = nonNullViewModel,
-                modifier = modifier,
+                modifier = modifier.sysuiResTag("bouncer_text_entry")
             )
         is PasswordBouncerViewModel ->
             PasswordBouncer(
                 viewModel = nonNullViewModel,
-                modifier = modifier,
+                modifier = modifier.sysuiResTag("bouncer_text_entry")
             )
         else -> Unit
     }
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..7fb88e8 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
@@ -27,13 +27,14 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneActionsViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerUserActionsViewModel
 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
+import com.android.systemui.scene.ui.composable.Scene
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -53,20 +54,19 @@
 class BouncerScene
 @Inject
 constructor(
-    private val actionsViewModelFactory: BouncerSceneActionsViewModel.Factory,
+    private val actionsViewModelFactory: BouncerUserActionsViewModel.Factory,
     private val contentViewModelFactory: BouncerSceneContentViewModel.Factory,
     private val dialogFactory: BouncerDialogFactory,
-) : ComposableScene {
+) : ExclusiveActivatable(), Scene {
     override val key = Scenes.Bouncer
 
-    private val actionsViewModel: BouncerSceneActionsViewModel by lazy {
+    private val actionsViewModel: BouncerUserActionsViewModel by lazy {
         actionsViewModelFactory.create()
     }
 
-    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        actionsViewModel.actions
+    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -75,7 +75,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/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 4ec0d99..480e4e4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -93,11 +93,15 @@
         }
     }
 
+    // set the focus, so adb can send the key events for testing.
+    val focusRequester = FocusRequester()
+    LaunchedEffect(Unit) { focusRequester.requestFocus() }
+
     VerticalGrid(
         columns = columns,
         verticalSpacing = verticalSpacing,
         horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp),
-        modifier = modifier,
+        modifier = modifier.focusRequester(focusRequester)
     ) {
         repeat(9) { index ->
             DigitButton(
@@ -285,26 +289,26 @@
     Box(
         contentAlignment = Alignment.Center,
         modifier =
-        modifier
-            .focusRequester(FocusRequester.Default)
-            .focusable()
-            .sizeIn(maxWidth = pinButtonMaxSize, maxHeight = pinButtonMaxSize)
-            .aspectRatio(1f)
-            .drawBehind {
-                drawRoundRect(
-                    color = containerColor,
-                    cornerRadius = CornerRadius(cornerRadius.toPx()),
-                )
-            }
-            .clip(CircleShape)
-            .thenIf(isEnabled) {
-                Modifier.combinedClickable(
-                    interactionSource = interactionSource,
-                    indication = indication,
-                    onClick = onClicked,
-                    onLongClick = onLongPressed
-                )
-            },
+            modifier
+                .focusRequester(FocusRequester.Default)
+                .focusable()
+                .sizeIn(maxWidth = pinButtonMaxSize, maxHeight = pinButtonMaxSize)
+                .aspectRatio(1f)
+                .drawBehind {
+                    drawRoundRect(
+                        color = containerColor,
+                        cornerRadius = CornerRadius(cornerRadius.toPx()),
+                    )
+                }
+                .clip(CircleShape)
+                .thenIf(isEnabled) {
+                    Modifier.combinedClickable(
+                        interactionSource = interactionSource,
+                        indication = indication,
+                        onClick = onClicked,
+                        onLongClick = onLongPressed
+                    )
+                },
     ) {
         content(contentColor::value)
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
index 465eade..ba885f7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -34,8 +34,10 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
@@ -351,14 +353,17 @@
 
     @Composable
     fun Content(modifier: Modifier) {
-        Row(
-            modifier =
+
+        // Wrap PIN entry in a Box so it is visible to accessibility (even if empty).
+        Box(modifier = modifier.fillMaxWidth().wrapContentHeight()) {
+            Row(
                 modifier
                     .heightIn(min = shapeAnimations.shapeSize)
                     // Pins overflowing horizontally should still be shown as scrolling.
-                    .wrapContentSize(unbounded = true),
-        ) {
-            entries.forEach { entry -> key(entry.digit) { entry.Content() } }
+                    .wrapContentSize(unbounded = true)
+            ) {
+                entries.forEach { entry -> key(entry.digit) { entry.Content() } }
+            }
         }
     }
 
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 91a88bc..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)
         }
     }
 }
@@ -1154,7 +1163,7 @@
                 .then(selectableModifier)
                 .thenIf(!viewModel.isEditMode && !model.inQuietMode) {
                     Modifier.pointerInput(Unit) {
-                        observeTaps { viewModel.onTapWidget(model.componentName, model.priority) }
+                        observeTaps { viewModel.onTapWidget(model.componentName, model.rank) }
                     }
                 }
                 .thenIf(!viewModel.isEditMode && model.inQuietMode) {
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..8b6de6a 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.scene.ui.composable.Scene
 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,17 +46,21 @@
     private val dialogFactory: SystemUIDialogFactory,
     private val interactionHandler: WidgetInteractionHandler,
     private val widgetSection: CommunalAppWidgetSection,
-) : ComposableScene {
+) : ExclusiveActivatable(), Scene {
     override val key = Scenes.Communal
 
-    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        MutableStateFlow<Map<UserAction, UserActionResult>>(
+    override val userActions: Flow<Map<UserAction, UserActionResult>> =
+        MutableStateFlow(
                 mapOf(
-                    Swipe(SwipeDirection.End) to UserActionResult(Scenes.Lockscreen),
+                    Swipe(SwipeDirection.End) to Scenes.Lockscreen,
                 )
             )
             .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/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 38a3474..1137357 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -34,11 +34,11 @@
     return remember(communalContent) {
         ContentListState(
             communalContent,
-            { componentName, user, priority ->
+            { componentName, user, rank ->
                 viewModel.onAddWidget(
                     componentName,
                     user,
-                    priority,
+                    rank,
                     widgetConfigurator,
                 )
             },
@@ -56,10 +56,9 @@
 class ContentListState
 internal constructor(
     communalContent: List<CommunalContentModel>,
-    private val onAddWidget:
-        (componentName: ComponentName, user: UserHandle, priority: Int) -> Unit,
-    private val onDeleteWidget: (id: Int, componentName: ComponentName, priority: Int) -> Unit,
-    private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
+    private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit,
+    private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit,
+    private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit,
 ) {
     var list = communalContent.toMutableStateList()
         private set
@@ -74,7 +73,7 @@
         if (list[indexToRemove].isWidgetContent()) {
             val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
             list.apply { removeAt(indexToRemove) }
-            onDeleteWidget(widget.appWidgetId, widget.componentName, widget.priority)
+            onDeleteWidget(widget.appWidgetId, widget.componentName, widget.rank)
         }
     }
 
@@ -94,24 +93,24 @@
         newItemUser: UserHandle? = null,
         newItemIndex: Int? = null
     ) {
-        // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
-        // in the list. When persisted in DB, this leaves space for the new item (to be added) at
-        // the correct priority.
-        val widgetIdToPriorityMap: Map<Int, Int> =
+        // New widget added to the grid. Other widgets are shifted as needed at the database level.
+        if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
+            onAddWidget(newItemComponentName, newItemUser, /* rank= */ newItemIndex)
+            return
+        }
+
+        // No new widget, only reorder existing widgets.
+        val widgetIdToRankMap: Map<Int, Int> =
             list
                 .mapIndexedNotNull { index, item ->
                     if (item is CommunalContentModel.WidgetContent) {
-                        item.appWidgetId to list.size - index
+                        item.appWidgetId to index
                     } else {
                         null
                     }
                 }
                 .toMap()
-        // reorder and then add the new widget
-        onReorderWidgets(widgetIdToPriorityMap)
-        if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
-            onAddWidget(newItemComponentName, newItemUser, /* priority= */ list.size - newItemIndex)
-        }
+        onReorderWidgets(widgetIdToRankMap)
     }
 
     /** Returns true if the item at given index is editable. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 0c29394..f2f7c87 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -193,7 +193,7 @@
             val widgetExtra = event.maybeWidgetExtra() ?: return false
             val (componentName, user) = widgetExtra
             if (componentName != null && user != null) {
-                // Placeholder isn't removed yet to allow the setting the right priority for items
+                // Placeholder isn't removed yet to allow the setting the right rank for items
                 // before adding in the new item.
                 contentListState.onSaveList(
                     newItemComponentName = componentName,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
index 04bcc36..c60e11e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt
@@ -73,8 +73,7 @@
             initialValue = null
         )
 
-    // TODO (b/353955910): back handling doesn't work
-    BackHandler { alternateBouncerDependencies.viewModel.onBackRequested() }
+    BackHandler(enabled = isVisible) { alternateBouncerDependencies.viewModel.onBackRequested() }
 
     AnimatedVisibility(
         visible = isVisible,
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..c7c29f9 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
@@ -22,12 +22,13 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
-import com.android.compose.animation.scene.animateSceneFloatAsState
+import com.android.compose.animation.scene.animateContentFloatAsState
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenUserActionsViewModel
+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
+import com.android.systemui.scene.ui.composable.Scene
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -37,19 +38,18 @@
 class LockscreenScene
 @Inject
 constructor(
-    actionsViewModelFactory: LockscreenSceneActionsViewModel.Factory,
+    actionsViewModelFactory: LockscreenUserActionsViewModel.Factory,
     private val lockscreenContent: Lazy<LockscreenContent>,
-) : ComposableScene {
+) : ExclusiveActivatable(), Scene {
     override val key = Scenes.Lockscreen
 
-    private val actionsViewModel: LockscreenSceneActionsViewModel by lazy {
+    private val actionsViewModel: LockscreenUserActionsViewModel by lazy {
         actionsViewModelFactory.create()
     }
 
-    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        actionsViewModel.actions
+    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -69,7 +69,7 @@
     lockscreenContent: Lazy<LockscreenContent>,
     modifier: Modifier = Modifier,
 ) {
-    animateSceneFloatAsState(
+    animateContentFloatAsState(
         value = QuickSettings.SharedValues.SquishinessValues.LockscreenSceneStarting,
         key = QuickSettings.SharedValues.TilesSquishiness,
     )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index a3e0701..3e73057 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -76,6 +76,11 @@
             viewModel
                 .areNotificationsVisible(contentKey)
                 .collectAsStateWithLifecycle(initialValue = false)
+        val isBypassEnabled by viewModel.isBypassEnabled.collectAsStateWithLifecycle()
+
+        if (isBypassEnabled) {
+            with(notificationSection) { HeadsUpNotifications() }
+        }
 
         LockscreenLongPress(
             viewModel = viewModel.touchHandling,
@@ -110,7 +115,7 @@
                                             }
                                 )
                             }
-                            if (isShadeLayoutWide) {
+                            if (isShadeLayoutWide && !isBypassEnabled) {
                                 with(notificationSection) {
                                     Notifications(
                                         areNotificationsVisible = areNotificationsVisible,
@@ -124,7 +129,7 @@
                                 }
                             }
                         }
-                        if (!isShadeLayoutWide) {
+                        if (!isShadeLayoutWide && !isBypassEnabled) {
                             with(notificationSection) {
                                 Notifications(
                                     areNotificationsVisible = areNotificationsVisible,
@@ -175,7 +180,7 @@
                 },
                 modifier = Modifier.fillMaxSize(),
             ) { measurables, constraints ->
-                check(measurables.size == 6)
+                check(measurables.size == 6) { "Expected 6 measurables, got: ${measurables.size}" }
                 val aboveLockIconMeasurable = measurables[0]
                 val lockIconMeasurable = measurables[1]
                 val belowLockIconMeasurable = measurables[2]
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index bcdb259..eae46e9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -111,7 +111,7 @@
                     1f
                 }
 
-            val dir = if (transition.toScene == splitShadeLargeClockScene) -1f else 1f
+            val dir = if (transition.toContent == splitShadeLargeClockScene) -1f else 1f
             val distance = dir * getClockCenteringDistance()
             val largeClock = checkNotNull(currentClock).largeClock
             largeClock.animations.onPositionUpdated(
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 6801cf2..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
@@ -20,7 +20,6 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.Dp
@@ -34,6 +33,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -79,6 +79,14 @@
         )
     }
 
+    @Composable
+    fun SceneScope.HeadsUpNotifications() {
+        SnoozeableHeadsUpNotificationSpace(
+            stackScrollView = stackScrollView.get(),
+            viewModel = rememberViewModel("HeadsUpNotifications") { viewModelFactory.create() },
+        )
+    }
+
     /**
      * @param burnInParams params to make this view adaptive to burn-in, `null` to disable burn-in
      *   adjustment
@@ -99,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/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 0eeb79b..97d89a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -93,7 +93,7 @@
         // Update state whenever currentSceneKey has changed.
         LaunchedEffect(state, currentScene) {
             if (currentScene != state.transitionState.currentScene) {
-                state.setTargetScene(currentScene, coroutineScope = this)
+                state.setTargetScene(currentScene, animationScope = this)
             }
         }
 
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/media/controls/ui/composable/MediaContentPicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt
index 70c0db1..5dccb68 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaContentPicker.kt
@@ -21,7 +21,7 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneTransitionLayoutState
 import com.android.compose.animation.scene.StaticElementContentPicker
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.systemui.scene.shared.model.Scenes
 
 /** [ElementContentPicker] implementation for the media carousel object. */
@@ -38,7 +38,7 @@
 
     override fun contentDuringTransition(
         element: ElementKey,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         fromContentZIndex: Float,
         toContentZIndex: Float
     ): ContentKey {
@@ -64,7 +64,7 @@
     }
 
     /** Returns true when the media should be laid on top of the rest for the given [transition]. */
-    fun shouldElevateMedia(transition: ContentState.Transition<*>): Boolean {
+    fun shouldElevateMedia(transition: TransitionState.Transition): Boolean {
         return transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
     }
 }
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 0bef05dc..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
@@ -68,7 +68,6 @@
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.unit.Dp
@@ -82,7 +81,6 @@
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.thenIf
-import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.res.R
@@ -137,14 +135,16 @@
                 .notificationHeadsUpHeight(stackScrollView)
                 .debugBackground(viewModel, DEBUG_HUN_COLOR)
                 .onGloballyPositioned { coordinates: LayoutCoordinates ->
+                    val positionInWindow = coordinates.positionInWindow()
                     val boundsInWindow = coordinates.boundsInWindow()
                     debugLog(viewModel) {
                         "HUNS onGloballyPositioned:" +
                             " size=${coordinates.size}" +
                             " bounds=$boundsInWindow"
                     }
-                    // Note: boundsInWindow doesn't scroll off the screen
-                    stackScrollView.setHeadsUpTop(boundsInWindow.top)
+                    // Note: boundsInWindow doesn't scroll off the screen, so use positionInWindow
+                    // for top bound, which can scroll off screen while snoozing
+                    stackScrollView.setHeadsUpTop(positionInWindow.y)
                     stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
                 }
     )
@@ -159,16 +159,10 @@
     stackScrollView: NotificationScrollView,
     viewModel: NotificationsPlaceholderViewModel,
 ) {
-    val context = LocalContext.current
-    val density = LocalDensity.current
-    val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
-    val headsUpPadding =
-        with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
-
     val isHeadsUp by viewModel.isHeadsUpOrAnimatingAway.collectAsStateWithLifecycle(false)
 
     var scrollOffset by remember { mutableFloatStateOf(0f) }
-    val minScrollOffset = -(statusBarHeight + headsUpPadding.toFloat())
+    val minScrollOffset = -(stackScrollView.getHeadsUpInset().toFloat())
     val maxScrollOffset = 0f
 
     val scrollableState = rememberScrollableState { delta ->
@@ -327,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/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
new file mode 100644
index 0000000..a22becc
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -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 com.android.systemui.notifications.ui.composable
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
+import com.android.systemui.scene.session.ui.composable.SaveableSession
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.ui.composable.Overlay
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
+import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class NotificationsShadeOverlay
+@Inject
+constructor(
+    private val actionsViewModelFactory: NotificationsShadeOverlayActionsViewModel.Factory,
+    private val contentViewModelFactory: NotificationsShadeOverlayContentViewModel.Factory,
+    private val tintedIconManagerFactory: TintedIconManager.Factory,
+    private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
+    private val statusBarIconController: StatusBarIconController,
+    private val shadeSession: SaveableSession,
+    private val stackScrollView: Lazy<NotificationScrollView>,
+) : Overlay {
+
+    override val key = Overlays.NotificationsShade
+
+    private val actionsViewModel: NotificationsShadeOverlayActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
+    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
+
+    override suspend fun activate(): Nothing {
+        actionsViewModel.activate()
+    }
+
+    @Composable
+    override fun ContentScope.Content(
+        modifier: Modifier,
+    ) {
+        val viewModel =
+            rememberViewModel("NotificationsShadeOverlay-viewModel") {
+                contentViewModelFactory.create()
+            }
+        val placeholderViewModel =
+            rememberViewModel("NotificationsShadeOverlay-notifPlaceholderViewModel") {
+                viewModel.notificationsPlaceholderViewModelFactory.create()
+            }
+
+        OverlayShade(
+            modifier = modifier,
+            onScrimClicked = viewModel::onScrimClicked,
+        ) {
+            Column {
+                ExpandedShadeHeader(
+                    viewModelFactory = viewModel.shadeHeaderViewModelFactory,
+                    createTintedIconManager = tintedIconManagerFactory::create,
+                    createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
+                    statusBarIconController = statusBarIconController,
+                    modifier = Modifier.padding(horizontal = 16.dp),
+                )
+
+                NotificationScrollingStack(
+                    shadeSession = shadeSession,
+                    stackScrollView = stackScrollView.get(),
+                    viewModel = placeholderViewModel,
+                    maxScrimTop = { 0f },
+                    shouldPunchHoleBehindScrim = false,
+                    shouldFillMaxSize = false,
+                    shouldReserveSpaceForNavBar = false,
+                    shadeMode = ShadeMode.Dual,
+                    modifier = Modifier.fillMaxWidth(),
+                )
+
+                // Communicates the bottom position of the drawable area within the shade to NSSL.
+                NotificationStackCutoffGuideline(
+                    stackScrollView = stackScrollView.get(),
+                    viewModel = placeholderViewModel,
+                )
+            }
+        }
+    }
+}
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..1f4cd04 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
@@ -27,23 +27,21 @@
 import com.android.compose.animation.scene.UserActionResult
 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.notifications.ui.viewmodel.NotificationsShadeUserActionsViewModel
 import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.Scene
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
 import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
 import dagger.Lazy
-import java.util.Optional
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -51,8 +49,7 @@
 class NotificationsShadeScene
 @Inject
 constructor(
-    private val actionsViewModelFactory: NotificationsShadeSceneActionsViewModel.Factory,
-    private val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
+    private val actionsViewModelFactory: NotificationsShadeUserActionsViewModel.Factory,
     private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
     private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
@@ -60,19 +57,17 @@
     private val statusBarIconController: StatusBarIconController,
     private val shadeSession: SaveableSession,
     private val stackScrollView: Lazy<NotificationScrollView>,
-    private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
-) : ComposableScene {
+) : ExclusiveActivatable(), Scene {
 
     override val key = Scenes.NotificationsShade
 
-    private val actionsViewModel: NotificationsShadeSceneActionsViewModel by lazy {
+    private val actionsViewModel: NotificationsShadeUserActionsViewModel by lazy {
         actionsViewModelFactory.create()
     }
 
-    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        actionsViewModel.actions
+    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -80,14 +75,14 @@
     override fun SceneScope.Content(
         modifier: Modifier,
     ) {
-        val notificationsPlaceholderViewModel = rememberViewModel {
-            notificationsPlaceholderViewModelFactory.create()
-        }
+        val notificationsPlaceholderViewModel =
+            rememberViewModel("NotificationsShadeScene") {
+                notificationsPlaceholderViewModelFactory.create()
+            }
 
         OverlayShade(
             modifier = modifier,
-            viewModelFactory = overlayShadeViewModelFactory,
-            lockscreenContent = lockscreenContent,
+            onScrimClicked = {},
         ) {
             Column {
                 ExpandedShadeHeader(
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 f399436..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,29 +98,31 @@
                 else -> QSSceneAdapter.State.CLOSED
             }
         }
-        is TransitionState.Transition ->
+        is TransitionState.Transition.ChangeScene ->
             with(transitionState) {
                 when {
                     isSplitShade -> UnsquishingQS(squishiness)
                     fromScene == Scenes.Shade && toScene == Scenes.QuickSettings -> {
-                        Expanding(progress)
+                        Expanding { progress }
                     }
                     fromScene == Scenes.QuickSettings && toScene == Scenes.Shade -> {
-                        Collapsing(progress)
+                        Collapsing { progress }
                     }
-                    fromScene == Scenes.Shade || toScene == Scenes.Shade -> {
+                    fromContent == Scenes.Shade || toContent == Scenes.Shade -> {
                         UnsquishingQQS(squishiness)
                     }
-                    fromScene == Scenes.QuickSettings || toScene == Scenes.QuickSettings -> {
+                    fromContent == Scenes.QuickSettings || toContent == Scenes.QuickSettings -> {
                         QSSceneAdapter.State.QS
                     }
                     else ->
                         error(
-                            "Bad transition for QuickSettings: fromScene=$fromScene," +
-                                " toScene=$toScene"
+                            "Bad transition for QuickSettings: fromContent=$fromContent," +
+                                " toScene=$toContent"
                         )
                 }
             }
+        is TransitionState.Transition.OverlayTransition ->
+            TODO("b/359173565: Handle overlay transitions")
     }
 }
 
@@ -212,7 +214,8 @@
                             addView(view)
                         }
                     },
-                    // When the view changes (e.g. due to a theme change), this will be recomposed
+                    // When the view changes (e.g. due to a theme change), this will be
+                    // recomposed
                     // if needed and the new view will be attached to the FrameLayout here.
                     update = {
                         qsSceneAdapter.setState(state())
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 e064724..d34295e 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
@@ -92,12 +93,12 @@
 import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.InQS
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneActionsViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneContentViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsUserActionsViewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.Scene
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
@@ -112,9 +113,11 @@
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 
 /** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class QuickSettingsScene
 @Inject
@@ -122,24 +125,23 @@
     private val shadeSession: SaveableSession,
     private val notificationStackScrollView: Lazy<NotificationScrollView>,
     private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
-    private val actionsViewModelFactory: QuickSettingsSceneActionsViewModel.Factory,
+    private val actionsViewModelFactory: QuickSettingsUserActionsViewModel.Factory,
     private val contentViewModelFactory: QuickSettingsSceneContentViewModel.Factory,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
     private val statusBarIconController: StatusBarIconController,
     private val mediaCarouselController: MediaCarouselController,
     @Named(MediaModule.QS_PANEL) private val mediaHost: MediaHost,
-) : ComposableScene {
+) : ExclusiveActivatable(), Scene {
     override val key = Scenes.QuickSettings
 
-    private val actionsViewModel: QuickSettingsSceneActionsViewModel by lazy {
+    private val actionsViewModel: QuickSettingsUserActionsViewModel by lazy {
         actionsViewModelFactory.create()
     }
 
-    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        actionsViewModel.actions
+    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -151,7 +153,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 +182,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(
@@ -253,7 +258,7 @@
         val isScrollable =
             when (val state = layoutState.transitionState) {
                 is TransitionState.Idle -> true
-                is TransitionState.Transition -> state.fromScene == Scenes.QuickSettings
+                is TransitionState.Transition -> state.fromContent == Scenes.QuickSettings
             }
 
         LaunchedEffect(isCustomizing, scrollState) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
new file mode 100644
index 0000000..f8d0588
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.composable
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.qs.panels.ui.compose.EditMode
+import com.android.systemui.qs.panels.ui.compose.TileGrid
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.ui.composable.Overlay
+import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
+import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class QuickSettingsShadeOverlay
+@Inject
+constructor(
+    private val actionsViewModelFactory: QuickSettingsShadeOverlayActionsViewModel.Factory,
+    private val contentViewModelFactory: QuickSettingsShadeOverlayContentViewModel.Factory,
+    private val tintedIconManagerFactory: TintedIconManager.Factory,
+    private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
+    private val statusBarIconController: StatusBarIconController,
+) : Overlay {
+
+    override val key = Overlays.QuickSettingsShade
+
+    private val actionsViewModel: QuickSettingsShadeOverlayActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
+    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
+
+    override suspend fun activate(): Nothing {
+        actionsViewModel.activate()
+    }
+
+    @Composable
+    override fun ContentScope.Content(
+        modifier: Modifier,
+    ) {
+        val viewModel =
+            rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() }
+
+        OverlayShade(
+            modifier = modifier,
+            onScrimClicked = viewModel::onScrimClicked,
+        ) {
+            Column {
+                ExpandedShadeHeader(
+                    viewModelFactory = viewModel.shadeHeaderViewModelFactory,
+                    createTintedIconManager = tintedIconManagerFactory::create,
+                    createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
+                    statusBarIconController = statusBarIconController,
+                    modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding),
+                )
+
+                ShadeBody(
+                    viewModel = viewModel.quickSettingsContainerViewModel,
+                )
+            }
+        }
+    }
+}
+
+@Composable
+fun ShadeBody(
+    viewModel: QuickSettingsContainerViewModel,
+) {
+    val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
+
+    AnimatedContent(
+        targetState = isEditing,
+        transitionSpec = { fadeIn(tween(500)) togetherWith fadeOut(tween(500)) }
+    ) { editing ->
+        if (editing) {
+            EditMode(
+                viewModel = viewModel.editModeViewModel,
+                modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding)
+            )
+        } else {
+            QuickSettingsLayout(
+                viewModel = viewModel,
+                modifier = Modifier.sysuiResTag("quick_settings_panel")
+            )
+        }
+    }
+}
+
+@Composable
+private fun QuickSettingsLayout(
+    viewModel: QuickSettingsContainerViewModel,
+    modifier: Modifier = Modifier,
+) {
+    Column(
+        verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        modifier = modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
+    ) {
+        BrightnessSliderContainer(
+            viewModel = viewModel.brightnessSliderViewModel,
+            modifier =
+                Modifier.fillMaxWidth()
+                    .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
+        )
+        TileGrid(
+            viewModel = viewModel.tileGridViewModel,
+            modifier =
+                Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
+            viewModel.editModeViewModel::startEditing,
+        )
+    }
+}
+
+object QuickSettingsShade {
+
+    object Dimensions {
+        val Padding = 16.dp
+        val BrightnessSliderHeight = 64.dp
+        val GridMaxHeight = 800.dp
+    }
+}
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..e27c7e2 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
@@ -16,50 +16,26 @@
 
 package com.android.systemui.qs.ui.composable
 
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.togetherWith
-import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.battery.BatteryMeterViewController
-import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
-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
-import com.android.systemui.qs.ui.composable.QuickSettingsShade.Transitions.QuickSettingsLayoutEnter
-import com.android.systemui.qs.ui.composable.QuickSettingsShade.Transitions.QuickSettingsLayoutExit
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneActionsViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneContentViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeUserActionsViewModel
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.Scene
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
 import com.android.systemui.shade.ui.composable.OverlayShade
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
-import dagger.Lazy
-import java.util.Optional
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -67,33 +43,36 @@
 class QuickSettingsShadeScene
 @Inject
 constructor(
-    private val actionsViewModelFactory: QuickSettingsShadeSceneActionsViewModel.Factory,
+    private val actionsViewModelFactory: QuickSettingsShadeUserActionsViewModel.Factory,
     private val contentViewModelFactory: QuickSettingsShadeSceneContentViewModel.Factory,
-    private val lockscreenContent: Lazy<Optional<LockscreenContent>>,
     private val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
     private val statusBarIconController: StatusBarIconController,
-) : ComposableScene {
+) : ExclusiveActivatable(), Scene {
 
     override val key = Scenes.QuickSettingsShade
 
-    private val actionsViewModel: QuickSettingsShadeSceneActionsViewModel by lazy {
+    private val actionsViewModel: QuickSettingsShadeUserActionsViewModel by lazy {
         actionsViewModelFactory.create()
     }
 
-    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        actionsViewModel.actions
+    override val userActions: 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,
             modifier = modifier,
+            onScrimClicked = {},
         ) {
             Column {
                 ExpandedShadeHeader(
@@ -111,68 +90,3 @@
         }
     }
 }
-
-@Composable
-fun ShadeBody(
-    viewModel: QuickSettingsContainerViewModel,
-) {
-    val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
-
-    AnimatedContent(
-        targetState = isEditing,
-        transitionSpec = { QuickSettingsLayoutEnter togetherWith QuickSettingsLayoutExit }
-    ) { editing ->
-        if (editing) {
-            EditMode(
-                viewModel = viewModel.editModeViewModel,
-                modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding)
-            )
-        } else {
-            QuickSettingsLayout(
-                viewModel = viewModel,
-                modifier = Modifier.sysuiResTag("quick_settings_panel")
-            )
-        }
-    }
-}
-
-@Composable
-private fun QuickSettingsLayout(
-    viewModel: QuickSettingsContainerViewModel,
-    modifier: Modifier = Modifier,
-) {
-    Column(
-        verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding),
-        horizontalAlignment = Alignment.CenterHorizontally,
-        modifier = modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
-    ) {
-        BrightnessSliderContainer(
-            viewModel = viewModel.brightnessSliderViewModel,
-            modifier =
-                Modifier.fillMaxWidth()
-                    .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
-        )
-        TileGrid(
-            viewModel = viewModel.tileGridViewModel,
-            modifier =
-                Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
-            viewModel.editModeViewModel::startEditing,
-        )
-    }
-}
-
-object QuickSettingsShade {
-
-    object Dimensions {
-        val Padding = 16.dp
-        val BrightnessSliderHeight = 64.dp
-        val GridMaxHeight = 800.dp
-    }
-
-    object Transitions {
-        val QuickSettingsLayoutEnter: EnterTransition = fadeIn(tween(500))
-        val QuickSettingsLayoutExit: ExitTransition = fadeOut(tween(500))
-        val QuickSettingsEditorEnter: EnterTransition = fadeIn(tween(500))
-        val QuickSettingsEditorExit: ExitTransition = fadeOut(tween(500))
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ActionableContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ActionableContent.kt
new file mode 100644
index 0000000..8fe6893
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ActionableContent.kt
@@ -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.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface for content that can respond to user-actions. */
+interface ActionableContent {
+    /**
+     * The mapping between [UserAction] and destination [UserActionResult]s.
+     *
+     * When the scene framework detects a user action, if the current scene has a map entry for that
+     * user action, the framework starts a transition to the content specified in the map.
+     *
+     * Once the content is shown, the scene framework will read this property and set up a collector
+     * to watch for new mapping values. For each map entry, the scene framework will set up user
+     * input handling for its [UserAction] and, if such a user action is detected, initiate a
+     * transition to the specified [UserActionResult].
+     *
+     * Note that reading from this method does _not_ mean that any user action has occurred.
+     * Instead, the property is read before any user action/gesture is detected so that the
+     * framework can decide whether to set up gesture/input detectors/listeners in case user actions
+     * of the given types ever occur.
+     *
+     * A missing value for a specific [UserAction] means that the user action of the given type is
+     * not currently active in the top-most content (in z-index order) and should be ignored by the
+     * framework until the top-most content changes.
+     */
+    val userActions: Flow<Map<UserAction, UserActionResult>>
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
deleted file mode 100644
index 3da6a02..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
+++ /dev/null
@@ -1,27 +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.scene.ui.composable
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.scene.shared.model.Scene
-
-/** Compose-capable extension of [Scene]. */
-interface ComposableScene : Scene {
-    @Composable fun SceneScope.Content(modifier: Modifier)
-}
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..ae5dd8a 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
@@ -23,16 +23,17 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
-import com.android.compose.animation.scene.animateSceneDpAsState
-import com.android.compose.animation.scene.animateSceneFloatAsState
+import com.android.compose.animation.scene.animateContentDpAsState
+import com.android.compose.animation.scene.animateContentFloatAsState
 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
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.Default
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.viewmodel.GoneSceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.GoneUserActionsViewModel
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import dagger.Lazy
@@ -49,16 +50,15 @@
 constructor(
     private val notificationStackScrolLView: Lazy<NotificationScrollView>,
     private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
-    private val viewModelFactory: GoneSceneActionsViewModel.Factory,
-) : ComposableScene {
+    private val viewModelFactory: GoneUserActionsViewModel.Factory,
+) : ExclusiveActivatable(), Scene {
     override val key = Scenes.Gone
 
-    private val actionsViewModel: GoneSceneActionsViewModel by lazy { viewModelFactory.create() }
+    private val actionsViewModel: GoneUserActionsViewModel by lazy { viewModelFactory.create() }
 
-    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        actionsViewModel.actions
+    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -66,15 +66,18 @@
     override fun SceneScope.Content(
         modifier: Modifier,
     ) {
-        animateSceneFloatAsState(
+        animateContentFloatAsState(
             value = QuickSettings.SharedValues.SquishinessValues.GoneSceneStarting,
             key = QuickSettings.SharedValues.TilesSquishiness,
         )
-        animateSceneDpAsState(value = Default, key = MediaLandscapeTopOffset, canOverflow = false)
+        animateContentDpAsState(value = Default, key = MediaLandscapeTopOffset, canOverflow = false)
         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..609ce90
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Overlay.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.systemui.scene.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.lifecycle.Activatable
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * 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, ActionableContent {
+    /** Uniquely-identifying key for this overlay. The key must be unique within its container. */
+    val key: OverlayKey
+
+    /**
+     * The user actions supported by this overlay.
+     *
+     * @see [ActionableContent.userActions]
+     */
+    override val userActions: Flow<Map<UserAction, UserActionResult>>
+        get() = flowOf(mapOf(Back to UserActionResult.HideOverlay(key)))
+
+    @Composable fun ContentScope.Content(modifier: Modifier)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt
new file mode 100644
index 0000000..8d8ab8e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt
@@ -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 com.android.systemui.scene.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.lifecycle.Activatable
+
+/**
+ * Defines interface for classes that can describe a "scene".
+ *
+ * In the scene framework, there can be multiple scenes in a single scene "container". The container
+ * takes care of rendering the current scene and allowing scenes to be switched from one to another
+ * based on either user action (for example, swiping down while on the lock screen scene may switch
+ * to the shade scene).
+ */
+interface Scene : Activatable, ActionableContent {
+
+    /** Uniquely-identifying key for this scene. The key must be unique within its container. */
+    val key: SceneKey
+
+    @Composable fun SceneScope.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..a7e41ce 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
@@ -49,17 +54,23 @@
  * containers.
  *
  * @param viewModel The UI state holder for this container.
- * @param sceneByKey Mapping of [ComposableScene] by [SceneKey], ordered by z-order such that the
- *   last scene is rendered on top of all other scenes. It's critical that this map contains exactly
- *   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 sceneByKey Mapping of [Scene] by [SceneKey], ordered by z-order such that the last scene
+ *   is rendered on top of all other scenes. It's critical that this map contains exactly 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>,
+    sceneByKey: Map<SceneKey, Scene>,
+    overlayByKey: Map<OverlayKey, Overlay>,
     initialSceneKey: SceneKey,
     dataSourceDelegator: SceneDataSourceDelegator,
     modifier: Modifier = Modifier,
@@ -86,39 +97,57 @@
         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)
+            sceneByKey[currentSceneKey]?.userActions?.collectLatest { 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) ->
+            sceneByKey.forEach { (sceneKey, scene) ->
                 scene(
                     key = sceneKey,
-                    userActions = userActionsBySceneKey.getOrDefault(sceneKey, emptyMap())
+                    userActions = userActionsByContentKey.getOrDefault(sceneKey, emptyMap())
                 ) {
                     // Activate the scene.
-                    LaunchedEffect(composableScene) { composableScene.activate() }
+                    LaunchedEffect(scene) { scene.activate() }
 
                     // Render the scene.
-                    with(composableScene) {
+                    with(scene) {
                         this@scene.Content(
                             modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(),
                         )
                     }
                 }
             }
+            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/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 39fc7ef..a0ebca2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,13 +1,11 @@
 package com.android.systemui.scene.ui.composable
 
 import androidx.compose.foundation.gestures.Orientation
-import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ProgressConverter
 import com.android.compose.animation.scene.transitions
 import com.android.systemui.bouncer.ui.composable.Bouncer
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
@@ -48,18 +46,8 @@
     // Scene transitions
 
     from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
-    from(Scenes.Gone, to = Scenes.NotificationsShade) {
-        goneToNotificationsShadeTransition(Edge.Top)
-    }
-    from(Scenes.Gone, to = Scenes.NotificationsShade, key = OpenBottomShade) {
-        goneToNotificationsShadeTransition(Edge.Bottom)
-    }
-    from(Scenes.Gone, to = Scenes.QuickSettingsShade) {
-        goneToQuickSettingsShadeTransition(Edge.Top)
-    }
-    from(Scenes.Gone, to = Scenes.QuickSettingsShade, key = OpenBottomShade) {
-        goneToQuickSettingsShadeTransition(Edge.Bottom)
-    }
+    from(Scenes.Gone, to = Scenes.NotificationsShade) { goneToNotificationsShadeTransition() }
+    from(Scenes.Gone, to = Scenes.QuickSettingsShade) { goneToQuickSettingsShadeTransition() }
     from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
     from(Scenes.Gone, to = Scenes.Shade, key = ToSplitShade) { goneToSplitShadeTransition() }
     from(Scenes.Gone, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) {
diff --git a/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..6738b97 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?,
@@ -59,7 +69,7 @@
         state.setTargetScene(
             targetScene = toScene,
             transitionKey = transitionKey,
-            coroutineScope = coroutineScope,
+            animationScope = coroutineScope,
         )
     }
 
@@ -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/FromGoneToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
index fb41374..48ec198 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToNotificationsShadeTransition.kt
@@ -16,12 +16,10 @@
 
 package com.android.systemui.scene.ui.composable.transitions
 
-import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
 
 fun TransitionBuilder.goneToNotificationsShadeTransition(
-    edge: Edge = Edge.Top,
     durationScale: Double = 1.0,
 ) {
-    toNotificationsShadeTransition(edge, durationScale)
+    toNotificationsShadeTransition(durationScale)
 }
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/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index 05949b2..337f53a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -19,12 +19,9 @@
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.TransitionBuilder
 import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
 import com.android.systemui.notifications.ui.composable.Notifications
 import com.android.systemui.shade.ui.composable.OverlayShade
 import com.android.systemui.shade.ui.composable.Shade
@@ -32,11 +29,6 @@
 import kotlin.time.Duration.Companion.milliseconds
 
 fun TransitionBuilder.toNotificationsShadeTransition(
-    /**
-     * The edge where the shade will animate from. This is statically determined (i.e. doesn't
-     * change during runtime).
-     */
-    edge: Edge = Edge.Top,
     durationScale: Double = 1.0,
 ) {
     spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
@@ -45,17 +37,11 @@
             stiffness = Spring.StiffnessMediumLow,
             visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
         )
-    distance =
-        object : UserActionDistance {
-            override fun UserActionDistanceScope.absoluteDistance(
-                fromSceneSize: IntSize,
-                orientation: Orientation,
-            ): Float {
-                return fromSceneSize.height.toFloat() * 2 / 3f
-            }
-        }
+    distance = UserActionDistance { fromSceneSize, orientation ->
+        fromSceneSize.height.toFloat() * 2 / 3f
+    }
 
-    translate(OverlayShade.Elements.Panel, edge)
+    translate(OverlayShade.Elements.Panel, Edge.Top)
 
     fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
 
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..8922224 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
@@ -40,56 +40,30 @@
 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.LowestZIndexContentPicker
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
-import com.android.systemui.keyguard.ui.composable.LockscreenContent
-import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.shared.model.ShadeAlignment
-import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
-import com.android.systemui.util.kotlin.getOrNull
-import dagger.Lazy
-import java.util.Optional
 
-/** The overlay shade renders a lightweight shade UI container on top of a background scene. */
+/** Renders a lightweight shade UI container, as an overlay. */
 @Composable
 fun SceneScope.OverlayShade(
-    viewModelFactory: OverlayShadeViewModel.Factory,
-    lockscreenContent: Lazy<Optional<LockscreenContent>>,
+    onScrimClicked: () -> Unit,
     modifier: Modifier = Modifier,
     content: @Composable () -> Unit,
 ) {
-    val viewModel = rememberViewModel { viewModelFactory.create() }
-    val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle()
-
     Box(modifier) {
-        if (backgroundScene == Scenes.Lockscreen) {
-            // Lockscreen content is optionally injected, because variants of System UI without a
-            // lockscreen cannot provide it.
-            val lockscreenContentOrNull = lockscreenContent.get().getOrNull()
-            lockscreenContentOrNull?.apply { Content(Modifier.fillMaxSize()) }
-        }
-
-        Scrim(onClicked = viewModel::onScrimClicked)
+        Scrim(onClicked = onScrimClicked)
 
         Box(
             modifier = Modifier.fillMaxSize().panelPadding(),
-            contentAlignment =
-                if (viewModel.panelAlignment == ShadeAlignment.Top) {
-                    Alignment.TopEnd
-                } else {
-                    Alignment.BottomEnd
-                },
+            contentAlignment = Alignment.TopEnd,
         ) {
             Panel(
                 modifier = Modifier.element(OverlayShade.Elements.Panel).panelSize(),
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..a03bf43 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
@@ -30,7 +30,7 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.displayCutoutPadding
+import androidx.compose.foundation.layout.displayCutout
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -47,8 +47,9 @@
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.CompositingStrategy
@@ -82,6 +83,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
@@ -98,17 +100,16 @@
 import com.android.systemui.notifications.ui.composable.NotificationStackCutoffGuideline
 import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
 import com.android.systemui.qs.ui.composable.BrightnessMirror
-import com.android.systemui.qs.ui.composable.QSMediaMeasurePolicy
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
 import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.InQQS
 import com.android.systemui.res.R
 import com.android.systemui.scene.session.ui.composable.SaveableSession
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.scene.ui.composable.Scene
 import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneActionsViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneContentViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeUserActionsViewModel
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import com.android.systemui.statusbar.phone.StatusBarLocation
@@ -119,6 +120,7 @@
 import javax.inject.Inject
 import javax.inject.Named
 import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 
 object Shade {
@@ -145,13 +147,14 @@
 }
 
 /** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class ShadeScene
 @Inject
 constructor(
     private val shadeSession: SaveableSession,
     private val notificationStackScrollView: Lazy<NotificationScrollView>,
-    private val actionsViewModelFactory: ShadeSceneActionsViewModel.Factory,
+    private val actionsViewModelFactory: ShadeUserActionsViewModel.Factory,
     private val contentViewModelFactory: ShadeSceneContentViewModel.Factory,
     private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
     private val tintedIconManagerFactory: TintedIconManager.Factory,
@@ -160,20 +163,19 @@
     private val mediaCarouselController: MediaCarouselController,
     @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
     @Named(QS_PANEL) private val qsMediaHost: MediaHost,
-) : ComposableScene {
+) : ExclusiveActivatable(), Scene {
 
     override val key = Scenes.Shade
 
-    private val actionsViewModel: ShadeSceneActionsViewModel by lazy {
+    private val actionsViewModel: ShadeUserActionsViewModel by lazy {
         actionsViewModelFactory.create()
     }
 
-    override suspend fun activate(): Nothing {
+    override suspend fun onActivated(): Nothing {
         actionsViewModel.activate()
     }
 
-    override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        actionsViewModel.actions
+    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
 
     @Composable
     override fun SceneScope.Content(
@@ -181,9 +183,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,
@@ -265,13 +270,14 @@
     shadeSession: SaveableSession,
 ) {
     val cutoutLocation = LocalDisplayCutout.current.location
+    val cutoutInsets = WindowInsets.Companion.displayCutout
     val isLandscape = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact
     val usingCollapsedLandscapeMedia =
         Utils.useCollapsedMediaInLandscape(LocalContext.current.resources)
     val isExpanded = !usingCollapsedLandscapeMedia || !isLandscape
     mediaHost.expansion = if (isExpanded) EXPANDED else COLLAPSED
 
-    val maxNotifScrimTop = remember { mutableStateOf(0f) }
+    var maxNotifScrimTop by remember { mutableIntStateOf(0) }
     val tileSquishiness by
         animateSceneFloatAsState(
             value = 1f,
@@ -297,6 +303,24 @@
             viewModel.qsSceneAdapter,
         )
     }
+    val shadeMeasurePolicy =
+        remember(mediaInRow) {
+            SingleShadeMeasurePolicy(
+                isMediaInRow = mediaInRow,
+                mediaOffset = { mediaOffset.roundToPx() },
+                onNotificationsTopChanged = { maxNotifScrimTop = it },
+                mediaZIndex = {
+                    if (MediaContentPicker.shouldElevateMedia(layoutState)) 1f else 0f
+                },
+                cutoutInsetsProvider = {
+                    if (cutoutLocation == CutoutLocation.CENTER) {
+                        null
+                    } else {
+                        cutoutInsets
+                    }
+                }
+            )
+        }
 
     Box(
         modifier =
@@ -314,101 +338,54 @@
                     .background(colorResource(R.color.shade_scrim_background_dark)),
         )
         Layout(
-            contents =
-                listOf(
-                    {
-                        Column(
-                            horizontalAlignment = Alignment.CenterHorizontally,
-                            modifier =
-                                Modifier.fillMaxWidth()
-                                    .thenIf(isEmptySpaceClickable) {
-                                        Modifier.clickable(
-                                            onClick = { viewModel.onEmptySpaceClicked() }
-                                        )
-                                    }
-                                    .thenIf(cutoutLocation != CutoutLocation.CENTER) {
-                                        Modifier.displayCutoutPadding()
-                                    },
-                        ) {
-                            CollapsedShadeHeader(
-                                viewModelFactory = viewModel.shadeHeaderViewModelFactory,
-                                createTintedIconManager = createTintedIconManager,
-                                createBatteryMeterViewController = createBatteryMeterViewController,
-                                statusBarIconController = statusBarIconController,
-                            )
-
-                            val content: @Composable () -> Unit = {
-                                Box(
-                                    Modifier.element(QuickSettings.Elements.QuickQuickSettings)
-                                        .layoutId(QSMediaMeasurePolicy.LayoutId.QS)
-                                ) {
-                                    QuickSettings(
-                                        viewModel.qsSceneAdapter,
-                                        { viewModel.qsSceneAdapter.qqsHeight },
-                                        isSplitShade = false,
-                                        squishiness = { tileSquishiness },
-                                    )
-                                }
-
-                                ShadeMediaCarousel(
-                                    isVisible = isMediaVisible,
-                                    mediaHost = mediaHost,
-                                    mediaOffsetProvider = mediaOffsetProvider,
-                                    modifier =
-                                        Modifier.layoutId(QSMediaMeasurePolicy.LayoutId.Media),
-                                    carouselController = mediaCarouselController,
-                                )
-                            }
-                            val landscapeQsMediaMeasurePolicy = remember {
-                                QSMediaMeasurePolicy(
-                                    { viewModel.qsSceneAdapter.qqsHeight },
-                                    { mediaOffset.roundToPx() },
-                                )
-                            }
-                            if (mediaInRow) {
-                                Layout(
-                                    content = content,
-                                    measurePolicy = landscapeQsMediaMeasurePolicy,
-                                )
-                            } else {
-                                content()
-                            }
-                        }
-                    },
-                    {
-                        NotificationScrollingStack(
-                            shadeSession = shadeSession,
-                            stackScrollView = notificationStackScrollView,
-                            viewModel = notificationsPlaceholderViewModel,
-                            maxScrimTop = { maxNotifScrimTop.value },
-                            shadeMode = ShadeMode.Single,
-                            shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
-                            onEmptySpaceClick =
-                                viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
-                        )
-                    },
+            modifier =
+                Modifier.thenIf(isEmptySpaceClickable) {
+                    Modifier.clickable { viewModel.onEmptySpaceClicked() }
+                },
+            content = {
+                CollapsedShadeHeader(
+                    viewModelFactory = viewModel.shadeHeaderViewModelFactory,
+                    createTintedIconManager = createTintedIconManager,
+                    createBatteryMeterViewController = createBatteryMeterViewController,
+                    statusBarIconController = statusBarIconController,
+                    modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
                 )
-        ) { measurables, constraints ->
-            check(measurables.size == 2)
-            check(measurables[0].size == 1)
-            check(measurables[1].size == 1)
 
-            val quickSettingsPlaceable = measurables[0][0].measure(constraints)
-            val notificationsPlaceable = measurables[1][0].measure(constraints)
+                Box(
+                    Modifier.element(QuickSettings.Elements.QuickQuickSettings)
+                        .layoutId(SingleShadeMeasurePolicy.LayoutId.QuickSettings)
+                ) {
+                    QuickSettings(
+                        viewModel.qsSceneAdapter,
+                        { viewModel.qsSceneAdapter.qqsHeight },
+                        isSplitShade = false,
+                        squishiness = { tileSquishiness },
+                    )
+                }
 
-            maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
+                ShadeMediaCarousel(
+                    isVisible = isMediaVisible,
+                    isInRow = mediaInRow,
+                    mediaHost = mediaHost,
+                    mediaOffsetProvider = mediaOffsetProvider,
+                    carouselController = mediaCarouselController,
+                    modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.Media),
+                )
 
-            layout(constraints.maxWidth, constraints.maxHeight) {
-                val qsZIndex =
-                    if (MediaContentPicker.shouldElevateMedia(layoutState)) {
-                        1f
-                    } else {
-                        0f
-                    }
-                quickSettingsPlaceable.placeRelative(x = 0, y = 0, zIndex = qsZIndex)
-                notificationsPlaceable.placeRelative(x = 0, y = maxNotifScrimTop.value.roundToInt())
-            }
-        }
+                NotificationScrollingStack(
+                    shadeSession = shadeSession,
+                    stackScrollView = notificationStackScrollView,
+                    viewModel = notificationsPlaceholderViewModel,
+                    maxScrimTop = { maxNotifScrimTop.toFloat() },
+                    shadeMode = ShadeMode.Single,
+                    shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
+                    onEmptySpaceClick =
+                        viewModel::onEmptySpaceClicked.takeIf { isEmptySpaceClickable },
+                    modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.Notifications),
+                )
+            },
+            measurePolicy = shadeMeasurePolicy,
+        )
         Box(
             modifier =
                 Modifier.align(Alignment.BottomCenter)
@@ -492,9 +469,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(
@@ -595,6 +573,7 @@
 
                             ShadeMediaCarousel(
                                 isVisible = isMediaVisible,
+                                isInRow = false,
                                 mediaHost = mediaHost,
                                 mediaOffsetProvider = mediaOffsetProvider,
                                 modifier =
@@ -652,6 +631,7 @@
 @Composable
 private fun SceneScope.ShadeMediaCarousel(
     isVisible: Boolean,
+    isInRow: Boolean,
     mediaHost: MediaHost,
     carouselController: MediaCarouselController,
     mediaOffsetProvider: ShadeMediaOffsetProvider,
@@ -663,7 +643,7 @@
         mediaHost = mediaHost,
         carouselController = carouselController,
         offsetProvider =
-            if (MediaContentPicker.shouldElevateMedia(layoutState)) {
+            if (isInRow || MediaContentPicker.shouldElevateMedia(layoutState)) {
                 null
             } else {
                 { mediaOffsetProvider.offset }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt
new file mode 100644
index 0000000..6275ac3
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/SingleShadeMeasurePolicy.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.composable
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.offset
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
+import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy.LayoutId
+import kotlin.math.max
+
+/**
+ * Lays out elements from the [LayoutId] in the shade. This policy supports the case when the QS and
+ * UMO share the same row and when they should be one below another.
+ */
+class SingleShadeMeasurePolicy(
+    private val isMediaInRow: Boolean,
+    private val mediaOffset: MeasureScope.() -> Int,
+    private val onNotificationsTopChanged: (Int) -> Unit,
+    private val mediaZIndex: () -> Float,
+    private val cutoutInsetsProvider: () -> WindowInsets?,
+) : MeasurePolicy {
+
+    enum class LayoutId {
+        QuickSettings,
+        Media,
+        Notifications,
+        ShadeHeader,
+    }
+
+    override fun MeasureScope.measure(
+        measurables: List<Measurable>,
+        constraints: Constraints,
+    ): MeasureResult {
+        val cutoutInsets: WindowInsets? = cutoutInsetsProvider()
+        val constraintsWithCutout = applyCutout(constraints, cutoutInsets)
+        val insetsLeft = cutoutInsets?.getLeft(this, layoutDirection) ?: 0
+        val insetsTop = cutoutInsets?.getTop(this) ?: 0
+
+        val shadeHeaderPlaceable =
+            measurables
+                .fastFirst { it.layoutId == LayoutId.ShadeHeader }
+                .measure(constraintsWithCutout)
+        val mediaPlaceable =
+            measurables
+                .fastFirstOrNull { it.layoutId == LayoutId.Media }
+                ?.measure(applyMediaConstraints(constraintsWithCutout, isMediaInRow))
+        val quickSettingsPlaceable =
+            measurables
+                .fastFirst { it.layoutId == LayoutId.QuickSettings }
+                .measure(constraintsWithCutout)
+        val notificationsPlaceable =
+            measurables.fastFirst { it.layoutId == LayoutId.Notifications }.measure(constraints)
+
+        val notificationsTop =
+            calculateNotificationsTop(
+                statusBarHeaderPlaceable = shadeHeaderPlaceable,
+                quickSettingsPlaceable = quickSettingsPlaceable,
+                mediaPlaceable = mediaPlaceable,
+                insetsTop = insetsTop,
+                isMediaInRow = isMediaInRow,
+            )
+        onNotificationsTopChanged(notificationsTop)
+
+        return layout(constraints.maxWidth, constraints.maxHeight) {
+            shadeHeaderPlaceable.placeRelative(x = insetsLeft, y = insetsTop)
+            quickSettingsPlaceable.placeRelative(
+                x = insetsLeft,
+                y = insetsTop + shadeHeaderPlaceable.height,
+            )
+
+            if (isMediaInRow) {
+                mediaPlaceable?.placeRelative(
+                    x = insetsLeft + constraintsWithCutout.maxWidth / 2,
+                    y = mediaOffset() + insetsTop + shadeHeaderPlaceable.height,
+                    zIndex = mediaZIndex(),
+                )
+            } else {
+                mediaPlaceable?.placeRelative(
+                    x = insetsLeft,
+                    y = insetsTop + shadeHeaderPlaceable.height + quickSettingsPlaceable.height,
+                    zIndex = mediaZIndex(),
+                )
+            }
+
+            // Notifications don't need to accommodate for horizontal insets
+            notificationsPlaceable.placeRelative(x = 0, y = notificationsTop)
+        }
+    }
+
+    private fun calculateNotificationsTop(
+        statusBarHeaderPlaceable: Placeable,
+        quickSettingsPlaceable: Placeable,
+        mediaPlaceable: Placeable?,
+        insetsTop: Int,
+        isMediaInRow: Boolean,
+    ): Int {
+        val mediaHeight = mediaPlaceable?.height ?: 0
+        return insetsTop +
+            statusBarHeaderPlaceable.height +
+            if (isMediaInRow) {
+                max(quickSettingsPlaceable.height, mediaHeight)
+            } else {
+                quickSettingsPlaceable.height + mediaHeight
+            }
+    }
+
+    private fun applyMediaConstraints(
+        constraints: Constraints,
+        isMediaInRow: Boolean,
+    ): Constraints {
+        return if (isMediaInRow) {
+            constraints.copy(maxWidth = constraints.maxWidth / 2)
+        } else {
+            constraints
+        }
+    }
+
+    private fun MeasureScope.applyCutout(
+        constraints: Constraints,
+        cutoutInsets: WindowInsets?,
+    ): Constraints {
+        return if (cutoutInsets == null) {
+            constraints
+        } else {
+            val left = cutoutInsets.getLeft(this, layoutDirection)
+            val top = cutoutInsets.getTop(this)
+            val right = cutoutInsets.getRight(this, layoutDirection)
+            val bottom = cutoutInsets.getBottom(this)
+
+            constraints.offset(horizontal = -(left + right), vertical = -(top + bottom))
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index 1db96cf..c9b8013 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -92,7 +92,12 @@
                         true
                     }
                 },
-            color = MaterialTheme.colorScheme.surface,
+            color =
+                if (enabled) {
+                    MaterialTheme.colorScheme.surface
+                } else {
+                    MaterialTheme.colorScheme.surfaceContainerHighest
+                },
             shape = RoundedCornerShape(28.dp),
             onClick =
                 if (enabled) {
@@ -119,7 +124,7 @@
                 modifier = Modifier.basicMarquee(),
                 text = connectedDeviceViewModel.label.toString(),
                 style = MaterialTheme.typography.labelMedium,
-                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                color = connectedDeviceViewModel.labelColor.toColor(),
                 maxLines = 1,
             )
             connectedDeviceViewModel.deviceName?.let {
@@ -127,7 +132,7 @@
                     modifier = Modifier.basicMarquee(),
                     text = it.toString(),
                     style = MaterialTheme.typography.titleMedium,
-                    color = MaterialTheme.colorScheme.onSurface,
+                    color = connectedDeviceViewModel.deviceNameColor.toColor(),
                     maxLines = 1,
                 )
             }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 072e91a..d4f3b5b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -23,7 +23,6 @@
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
 import androidx.compose.animation.expandVertically
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
@@ -78,7 +77,6 @@
     modifier: Modifier = Modifier,
 ) {
     require(viewModels.isNotEmpty())
-    val transition = updateTransition(isExpanded, label = "CollapsableSliders")
     Column(modifier = modifier) {
         Box(
             modifier = Modifier.fillMaxWidth(),
@@ -106,8 +104,9 @@
                 sliderColors = sliderColors,
             )
         }
-        transition.AnimatedVisibility(
-            visible = { it || !isExpandable },
+        AnimatedVisibility(
+            visible = isExpanded || !isExpandable,
+            label = "CollapsableSliders",
             enter =
                 expandVertically(animationSpec = tween(durationMillis = EXPAND_DURATION_MILLIS)),
             exit =
@@ -120,23 +119,31 @@
                     for (index in 1..viewModels.lastIndex) {
                         val sliderViewModel: SliderViewModel = viewModels[index]
                         val sliderState by sliderViewModel.slider.collectAsStateWithLifecycle()
-                        transition.AnimatedVisibility(
-                            modifier = Modifier.padding(top = 16.dp),
-                            visible = { it || !isExpandable },
-                            enter = enterTransition(index = index, totalCount = viewModels.size),
-                            exit = exitTransition(index = index, totalCount = viewModels.size)
-                        ) {
-                            VolumeSlider(
-                                modifier = Modifier.fillMaxWidth(),
-                                state = sliderState,
-                                onValueChange = { newValue: Float ->
-                                    sliderViewModel.onValueChanged(sliderState, newValue)
-                                },
-                                onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
-                                onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
-                                sliderColors = sliderColors,
-                            )
-                        }
+
+                        VolumeSlider(
+                            modifier =
+                                Modifier.padding(top = 16.dp)
+                                    .fillMaxWidth()
+                                    .animateEnterExit(
+                                        enter =
+                                            enterTransition(
+                                                index = index,
+                                                totalCount = viewModels.size,
+                                            ),
+                                        exit =
+                                            exitTransition(
+                                                index = index,
+                                                totalCount = viewModels.size,
+                                            ),
+                                    ),
+                            state = sliderState,
+                            onValueChange = { newValue: Float ->
+                                sliderViewModel.onValueChanged(sliderState, newValue)
+                            },
+                            onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
+                            onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
+                            sliderColors = sliderColors,
+                        )
                     }
                 }
             }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index 5ffb6f8..1cc0fb2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -25,13 +25,13 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.paneTitle
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.theme.PlatformTheme
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
@@ -43,7 +43,6 @@
 private val padding = 24.dp
 
 @Composable
-@OptIn(ExperimentalComposeUiApi::class)
 fun VolumePanelRoot(
     viewModel: VolumePanelViewModel,
     modifier: Modifier = Modifier,
@@ -54,18 +53,20 @@
 
     with(VolumePanelComposeScope(state)) {
         components?.let { componentsState ->
-            Components(
-                componentsState,
-                modifier
-                    .sysuiResTag(VolumePanelTestTag)
-                    .semantics { paneTitle = accessibilityTitle }
-                    .padding(
-                        start = padding,
-                        top = padding,
-                        end = padding,
-                        bottom = 20.dp,
-                    )
-            )
+            PlatformTheme {
+                Components(
+                    componentsState,
+                    modifier
+                        .sysuiResTag(VolumePanelTestTag)
+                        .semantics { paneTitle = accessibilityTitle }
+                        .padding(
+                            start = padding,
+                            top = padding,
+                            end = padding,
+                            bottom = 20.dp,
+                        )
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index 5eabd22..d876606 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -19,49 +19,34 @@
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.SpringSpec
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
 
 internal fun CoroutineScope.animateContent(
-    transition: ContentState.Transition<*>,
+    layoutState: MutableSceneTransitionLayoutStateImpl,
+    transition: TransitionState.Transition,
     oneOffAnimation: OneOffAnimation,
     targetProgress: Float,
-    startTransition: () -> Unit,
-    finishTransition: () -> Unit,
-) {
-    // Start the transition. This will compute the TransformationSpec associated to [transition],
-    // which we need to initialize the Animatable that will actually animate it.
-    startTransition()
-
-    // The transition now contains the transformation spec that we should use to instantiate the
-    // Animatable.
-    val animationSpec = transition.transformationSpec.progressSpec
-    val visibilityThreshold =
-        (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
-    val replacedTransition = transition.replacedTransition
-    val initialProgress = replacedTransition?.progress ?: 0f
-    val initialVelocity = replacedTransition?.progressVelocity ?: 0f
-    val animatable =
-        Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also {
-            oneOffAnimation.animatable = it
-        }
-
-    // Animate the progress to its target value.
-    //
-    // 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.
-    // Otherwise, this transition will never be stopped and we will never settle to Idle.
-    oneOffAnimation.job =
-        launch(start = CoroutineStart.ATOMIC) {
-            try {
-                animatable.animateTo(targetProgress, animationSpec, initialVelocity)
-            } finally {
-                finishTransition()
+    chain: Boolean = true,
+): Job {
+    oneOffAnimation.onRun = {
+        // Animate the progress to its target value.
+        val animationSpec = transition.transformationSpec.progressSpec
+        val visibilityThreshold =
+            (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
+        val replacedTransition = transition.replacedTransition
+        val initialProgress = replacedTransition?.progress ?: 0f
+        val initialVelocity = replacedTransition?.progressVelocity ?: 0f
+        val animatable =
+            Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also {
+                oneOffAnimation.animatable = it
             }
-        }
+
+        animatable.animateTo(targetProgress, animationSpec, initialVelocity)
+    }
+
+    return layoutState.startTransitionImmediately(animationScope = this, transition, chain)
 }
 
 internal class OneOffAnimation {
@@ -74,8 +59,8 @@
      */
     lateinit var animatable: Animatable<Float, AnimationVector1D>
 
-    /** The job that is animating [animatable]. */
-    lateinit var job: Job
+    /** The runnable to run for this animation. */
+    lateinit var onRun: suspend () -> Unit
 
     val progress: Float
         get() = animatable.value
@@ -83,7 +68,13 @@
     val progressVelocity: Float
         get() = animatable.velocity
 
-    fun finish(): Job = job
+    suspend fun run() {
+        onRun()
+    }
+
+    fun freezeAndAnimateToCurrentState() {
+        // Do nothing, the state of one-off animations never change and we directly animate to it.
+    }
 }
 
 // TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
new file mode 100644
index 0000000..28116cb
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateOverlay.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import com.android.compose.animation.scene.content.state.TransitionState
+import kotlinx.coroutines.CoroutineScope
+
+/** Trigger a one-off transition to show or hide an overlay. */
+internal fun CoroutineScope.showOrHideOverlay(
+    layoutState: MutableSceneTransitionLayoutStateImpl,
+    overlay: OverlayKey,
+    fromOrToScene: SceneKey,
+    isShowing: Boolean,
+    transitionKey: TransitionKey?,
+    replacedTransition: TransitionState.Transition.ShowOrHideOverlay?,
+    reversed: Boolean,
+): TransitionState.Transition.ShowOrHideOverlay {
+    val targetProgress = if (reversed) 0f else 1f
+    val (fromContent, toContent) =
+        if (isShowing xor reversed) {
+            fromOrToScene to overlay
+        } else {
+            overlay to fromOrToScene
+        }
+
+    val oneOffAnimation = OneOffAnimation()
+    val transition =
+        OneOffShowOrHideOverlayTransition(
+            overlay = overlay,
+            fromOrToScene = fromOrToScene,
+            fromContent = fromContent,
+            toContent = toContent,
+            isEffectivelyShown = isShowing,
+            key = transitionKey,
+            replacedTransition = replacedTransition,
+            oneOffAnimation = oneOffAnimation,
+        )
+
+    animateContent(
+        layoutState = layoutState,
+        transition = transition,
+        oneOffAnimation = oneOffAnimation,
+        targetProgress = targetProgress,
+    )
+
+    return transition
+}
+
+/** Trigger a one-off transition to replace an overlay by another one. */
+internal fun CoroutineScope.replaceOverlay(
+    layoutState: MutableSceneTransitionLayoutStateImpl,
+    fromOverlay: OverlayKey,
+    toOverlay: OverlayKey,
+    transitionKey: TransitionKey?,
+    replacedTransition: TransitionState.Transition.ReplaceOverlay?,
+    reversed: Boolean,
+): TransitionState.Transition.ReplaceOverlay {
+    val targetProgress = if (reversed) 0f else 1f
+    val effectivelyShownOverlay = if (reversed) fromOverlay else toOverlay
+
+    val oneOffAnimation = OneOffAnimation()
+    val transition =
+        OneOffOverlayReplacingTransition(
+            fromOverlay = fromOverlay,
+            toOverlay = toOverlay,
+            effectivelyShownOverlay = effectivelyShownOverlay,
+            key = transitionKey,
+            replacedTransition = replacedTransition,
+            oneOffAnimation = oneOffAnimation,
+        )
+
+    animateContent(
+        layoutState = layoutState,
+        transition = transition,
+        oneOffAnimation = oneOffAnimation,
+        targetProgress = targetProgress,
+    )
+
+    return transition
+}
+
+private class OneOffShowOrHideOverlayTransition(
+    overlay: OverlayKey,
+    fromOrToScene: SceneKey,
+    fromContent: ContentKey,
+    toContent: ContentKey,
+    override val isEffectivelyShown: Boolean,
+    override val key: TransitionKey?,
+    replacedTransition: TransitionState.Transition?,
+    private val oneOffAnimation: OneOffAnimation,
+) :
+    TransitionState.Transition.ShowOrHideOverlay(
+        overlay,
+        fromOrToScene,
+        fromContent,
+        toContent,
+        replacedTransition,
+    ) {
+    override val progress: Float
+        get() = oneOffAnimation.progress
+
+    override val progressVelocity: Float
+        get() = oneOffAnimation.progressVelocity
+
+    override val isInitiatedByUserInput: Boolean = false
+    override val isUserInputOngoing: Boolean = false
+
+    override suspend fun run() {
+        oneOffAnimation.run()
+    }
+
+    override fun freezeAndAnimateToCurrentState() {
+        oneOffAnimation.freezeAndAnimateToCurrentState()
+    }
+}
+
+private class OneOffOverlayReplacingTransition(
+    fromOverlay: OverlayKey,
+    toOverlay: OverlayKey,
+    override val effectivelyShownOverlay: OverlayKey,
+    override val key: TransitionKey?,
+    replacedTransition: TransitionState.Transition?,
+    private val oneOffAnimation: OneOffAnimation,
+) : TransitionState.Transition.ReplaceOverlay(fromOverlay, toOverlay, replacedTransition) {
+    override val progress: Float
+        get() = oneOffAnimation.progress
+
+    override val progressVelocity: Float
+        get() = oneOffAnimation.progressVelocity
+
+    override val isInitiatedByUserInput: Boolean = false
+    override val isUserInputOngoing: Boolean = false
+
+    override suspend fun run() {
+        oneOffAnimation.run()
+    }
+
+    override fun freezeAndAnimateToCurrentState() {
+        oneOffAnimation.freezeAndAnimateToCurrentState()
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index ae5a84b..b30f2b7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -393,45 +393,75 @@
         transition: TransitionState.Transition?,
     ): T? {
         if (transition == null) {
-            return sharedValue[layoutImpl.state.transitionState.currentScene]
+            return sharedValue[content]
+                ?: sharedValue[layoutImpl.state.transitionState.currentScene]
         }
 
-        val fromValue = sharedValue[transition.fromScene]
-        val toValue = sharedValue[transition.toScene]
-        return if (fromValue != null && toValue != null) {
-            if (fromValue == toValue) {
-                // Optimization: avoid reading progress if the values are the same, so we don't
-                // relayout/redraw for nothing.
-                fromValue
-            } else {
-                val overscrollSpec = transition.currentOverscrollSpec
-                val progress =
-                    when {
-                        overscrollSpec == null -> {
-                            if (canOverflow) transition.progress
-                            else transition.progress.fastCoerceIn(0f, 1f)
-                        }
-                        overscrollSpec.scene == transition.toScene -> 1f
-                        else -> 0f
-                    }
+        val fromValue = sharedValue[transition.fromContent]
+        val toValue = sharedValue[transition.toContent]
+        if (fromValue == null && toValue == null) {
+            return null
+        }
 
-                sharedValue.type.lerp(fromValue, toValue, progress)
+        if (fromValue != null && toValue != null) {
+            return interpolateSharedValue(fromValue, toValue, transition, sharedValue)
+        }
+
+        if (transition is TransitionState.Transition.ReplaceOverlay) {
+            val currentSceneValue = sharedValue[transition.currentScene]
+            if (currentSceneValue != null) {
+                return interpolateSharedValue(
+                    fromValue = fromValue ?: currentSceneValue,
+                    toValue = toValue ?: currentSceneValue,
+                    transition,
+                    sharedValue,
+                )
             }
-        } else fromValue ?: toValue
+        }
+
+        return fromValue ?: toValue
+    }
+
+    private fun interpolateSharedValue(
+        fromValue: T,
+        toValue: T,
+        transition: TransitionState.Transition,
+        sharedValue: SharedValue<T, *>,
+    ): T? {
+        if (fromValue == toValue) {
+            // Optimization: avoid reading progress if the values are the same, so we don't
+            // relayout/redraw for nothing.
+            return fromValue
+        }
+
+        val overscrollSpec = transition.currentOverscrollSpec
+        val progress =
+            when {
+                overscrollSpec == null -> {
+                    if (canOverflow) transition.progress
+                    else transition.progress.fastCoerceIn(0f, 1f)
+                }
+                overscrollSpec.content == transition.toContent -> 1f
+                else -> 0f
+            }
+
+        return sharedValue.type.lerp(fromValue, toValue, progress)
     }
 
     private fun transition(sharedValue: SharedValue<T, Delta>): TransitionState.Transition? {
         val targetValues = sharedValue.targetValues
         val transition =
             if (element != null) {
-                layoutImpl.elements[element]?.stateByContent?.let { sceneStates ->
-                    layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
-                        transition.fromScene in sceneStates || transition.toScene in sceneStates
-                    }
+                layoutImpl.elements[element]?.let { element ->
+                    elementState(
+                        layoutImpl.state.transitionStates,
+                        isInContent = { it in element.stateByContent },
+                    )
+                        as? TransitionState.Transition
                 }
             } else {
                 layoutImpl.state.currentTransitions.fastLastOrNull { transition ->
-                    transition.fromScene in targetValues || transition.toScene in targetValues
+                    transition.fromContent in targetValues || transition.toContent in targetValues
                 }
             }
 
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 920c234..86be4a4 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? {
+): Pair<TransitionState.Transition.ChangeScene, Job>? {
     val transitionState = layoutState.transitionState
     if (transitionState.currentScene == target) {
         // This can happen in 3 different situations, for which there isn't anything else to do:
@@ -43,16 +43,19 @@
     }
 
     return when (transitionState) {
-        is TransitionState.Idle -> {
+        is TransitionState.Idle,
+        is TransitionState.Transition.ShowOrHideOverlay,
+        is TransitionState.Transition.ReplaceOverlay -> {
             animateToScene(
                 layoutState,
                 target,
                 transitionKey,
                 isInitiatedByUserInput = false,
+                fromScene = transitionState.currentScene,
                 replacedTransition = null,
             )
         }
-        is TransitionState.Transition -> {
+        is TransitionState.Transition.ChangeScene -> {
             val isInitiatedByUserInput = transitionState.isInitiatedByUserInput
 
             // A transition is currently running: first check whether `transition.toScene` or
@@ -136,7 +139,7 @@
     reversed: Boolean = false,
     fromScene: SceneKey = layoutState.transitionState.currentScene,
     chain: Boolean = true,
-): TransitionState.Transition {
+): Pair<TransitionState.Transition.ChangeScene, Job> {
     val oneOffAnimation = OneOffAnimation()
     val targetProgress = if (reversed) 0f else 1f
     val transition =
@@ -162,15 +165,16 @@
             )
         }
 
-    animateContent(
-        transition = transition,
-        oneOffAnimation = oneOffAnimation,
-        targetProgress = targetProgress,
-        startTransition = { layoutState.startTransition(transition, chain) },
-        finishTransition = { layoutState.finishTransition(transition) },
-    )
+    val job =
+        animateContent(
+            layoutState = layoutState,
+            transition = transition,
+            oneOffAnimation = oneOffAnimation,
+            targetProgress = targetProgress,
+            chain = chain,
+        )
 
-    return transition
+    return transition to job
 }
 
 private class OneOffSceneTransition(
@@ -181,7 +185,7 @@
     override val isInitiatedByUserInput: Boolean,
     replacedTransition: TransitionState.Transition?,
     private val oneOffAnimation: OneOffAnimation,
-) : TransitionState.Transition(fromScene, toScene, replacedTransition) {
+) : TransitionState.Transition.ChangeScene(fromScene, toScene, replacedTransition) {
     override val progress: Float
         get() = oneOffAnimation.progress
 
@@ -190,5 +194,11 @@
 
     override val isUserInputOngoing: Boolean = false
 
-    override fun finish(): Job = oneOffAnimation.finish()
+    override suspend fun run() {
+        oneOffAnimation.run()
+    }
+
+    override fun freezeAndAnimateToCurrentState() {
+        oneOffAnimation.freezeAndAnimateToCurrentState()
+    }
 }
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 9c3896b..24fef71 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,28 +18,16 @@
 
 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.state.ContentState
-import com.android.compose.animation.scene.content.state.ContentState.HasOverscrollProperties.Companion.DistanceUnspecified
+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 {
     /**
@@ -64,17 +52,16 @@
     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(
     internal val layoutImpl: SceneTransitionLayoutImpl,
     internal val orientation: Orientation,
-    internal val coroutineScope: CoroutineScope,
 ) : DraggableHandler {
     internal val nestedScrollKey = Any()
     /** The [DraggableHandler] can only have one active [DragController] at a time. */
@@ -110,22 +97,25 @@
             return false
         }
 
-        val swipeTransition = dragController.swipeTransition
-
-        // Don't intercept a transition that is finishing.
-        if (swipeTransition.isFinishing) {
-            return false
-        }
+        val swipeAnimation = dragController.swipeAnimation
 
         // 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(
@@ -141,65 +131,68 @@
             }
 
             // 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
 
             // 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, 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,
@@ -255,215 +248,195 @@
 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.startTransitionImmediately(
+                animationScope = draggableHandler.layoutImpl.animationScope,
+                newTransition.contentTransition,
+                true
+            )
         }
 
-        swipeTransition = newTransition
+        swipeAnimation = newTransition
     }
 
     /**
      * 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.isAnimatingOffset()) {
             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.isAnimatingOffset()) {
             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.
@@ -514,329 +487,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(_fromScene.key, _toScene.key, replacedTransition),
-    ContentState.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?,
@@ -846,8 +498,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]
         }
@@ -857,39 +509,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 ->
@@ -897,18 +543,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(
@@ -1024,10 +658,6 @@
 
                 val canStart =
                     when (behavior) {
-                        NestedScrollBehavior.DuringTransitionBetweenScenes -> {
-                            canChangeScene = false // unused: added for consistency
-                            false
-                        }
                         NestedScrollBehavior.EdgeNoPreview -> {
                             canChangeScene = isZeroOffset
                             isZeroOffset && hasNextScene(offsetAvailable)
@@ -1090,7 +720,7 @@
                 val controller = dragController ?: error("Should be called after onStart")
 
                 controller
-                    .onStop(velocity = velocityAvailable, canChangeScene = canChangeScene)
+                    .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene)
                     .also { dragController = null }
             },
         )
@@ -1108,5 +738,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/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index e619814..56c08b9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -46,10 +46,9 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.fastCoerceIn
-import androidx.compose.ui.util.fastLastOrNull
+import androidx.compose.ui.util.fastForEachReversed
 import androidx.compose.ui.util.lerp
 import com.android.compose.animation.scene.content.Content
-import com.android.compose.animation.scene.content.state.ContentState
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
@@ -69,7 +68,7 @@
      * The last transition that was used when computing the state (size, position and alpha) of this
      * element in any content, or `null` if it was last laid out when idle.
      */
-    var lastTransition: ContentState.Transition<*>? = null
+    var lastTransition: TransitionState.Transition? = null
 
     /** Whether this element was ever drawn in a content. */
     var wasDrawnInAnyContent = false
@@ -146,8 +145,9 @@
     // layout/drawing.
     // TODO(b/341072461): Revert this and read the current transitions in ElementNode directly once
     // we can ensure that SceneTransitionLayoutImpl will compose new contents first.
-    val currentTransitions = layoutImpl.state.currentTransitions
-    return then(ElementModifier(layoutImpl, currentTransitions, content, key)).testTag(key.testTag)
+    val currentTransitionStates = layoutImpl.state.transitionStates
+    return then(ElementModifier(layoutImpl, currentTransitionStates, content, key))
+        .testTag(key.testTag)
 }
 
 /**
@@ -156,20 +156,21 @@
  */
 private data class ElementModifier(
     private val layoutImpl: SceneTransitionLayoutImpl,
-    private val currentTransitions: List<TransitionState.Transition>,
+    private val currentTransitionStates: List<TransitionState>,
     private val content: Content,
     private val key: ElementKey,
 ) : ModifierNodeElement<ElementNode>() {
-    override fun create(): ElementNode = ElementNode(layoutImpl, currentTransitions, content, key)
+    override fun create(): ElementNode =
+        ElementNode(layoutImpl, currentTransitionStates, content, key)
 
     override fun update(node: ElementNode) {
-        node.update(layoutImpl, currentTransitions, content, key)
+        node.update(layoutImpl, currentTransitionStates, content, key)
     }
 }
 
 internal class ElementNode(
     private var layoutImpl: SceneTransitionLayoutImpl,
-    private var currentTransitions: List<TransitionState.Transition>,
+    private var currentTransitionStates: List<TransitionState>,
     private var content: Content,
     private var key: ElementKey,
 ) : Modifier.Node(), DrawModifierNode, ApproachLayoutModifierNode, TraversableNode {
@@ -227,12 +228,12 @@
 
     fun update(
         layoutImpl: SceneTransitionLayoutImpl,
-        currentTransitions: List<TransitionState.Transition>,
+        currentTransitionStates: List<TransitionState>,
         content: Content,
         key: ElementKey,
     ) {
         check(layoutImpl == this.layoutImpl && content == this.content)
-        this.currentTransitions = currentTransitions
+        this.currentTransitionStates = currentTransitionStates
 
         removeNodeFromContentState()
 
@@ -288,31 +289,90 @@
         measurable: Measurable,
         constraints: Constraints,
     ): MeasureResult {
-        val transitions = currentTransitions
-        val transition = elementTransition(layoutImpl, element, transitions)
+        val elementState = elementState(layoutImpl, element, currentTransitionStates)
+        if (elementState == null) {
+            // If the element is not part of any transition, place it normally in its idle scene.
+            val currentState = currentTransitionStates.last()
+            val placeInThisContent =
+                elementContentWhenIdle(
+                    layoutImpl,
+                    currentState.currentScene,
+                    currentState.currentOverlays,
+                    isInContent = { it in element.stateByContent },
+                ) == content.key
+
+            return if (placeInThisContent) {
+                placeNormally(measurable, constraints)
+            } else {
+                doNotPlace(measurable, constraints)
+            }
+        }
+
+        val transition = elementState as? TransitionState.Transition
 
         // If this element is not supposed to be laid out now, either because it is not part of any
         // ongoing transition or the other content of its transition is overscrolling, then lay out
         // the element normally and don't place it.
-        val overscrollScene = transition?.currentOverscrollSpec?.scene
-        val isOtherSceneOverscrolling = overscrollScene != null && overscrollScene != content.key
-        val isNotPartOfAnyOngoingTransitions = transitions.isNotEmpty() && transition == null
-        if (isNotPartOfAnyOngoingTransitions || isOtherSceneOverscrolling) {
-            recursivelyClearPlacementValues()
-            stateInContent.lastSize = Element.SizeUnspecified
+        val overscrollContent = transition?.currentOverscrollSpec?.content
+        if (overscrollContent != null && overscrollContent != content.key) {
+            when (transition) {
+                is TransitionState.Transition.ChangeScene ->
+                    return doNotPlace(measurable, constraints)
 
-            val placeable = measurable.measure(constraints)
-            return layout(placeable.width, placeable.height) { /* Do not place */ }
+                // If we are overscrolling an overlay that does not contain an element that is in
+                // the current scene, place it in that scene otherwise the element won't be placed
+                // at all.
+                is TransitionState.Transition.ShowOrHideOverlay,
+                is TransitionState.Transition.ReplaceOverlay -> {
+                    if (
+                        content.key == transition.currentScene &&
+                            overscrollContent !in element.stateByContent
+                    ) {
+                        return placeNormally(measurable, constraints)
+                    } else {
+                        return doNotPlace(measurable, constraints)
+                    }
+                }
+            }
         }
 
         val placeable =
             measure(layoutImpl, element, transition, stateInContent, measurable, constraints)
         stateInContent.lastSize = placeable.size()
-        return layout(placeable.width, placeable.height) { place(transition, placeable) }
+        return layout(placeable.width, placeable.height) { place(elementState, placeable) }
+    }
+
+    private fun ApproachMeasureScope.doNotPlace(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        recursivelyClearPlacementValues()
+        stateInContent.lastSize = Element.SizeUnspecified
+
+        val placeable = measurable.measure(constraints)
+        return layout(placeable.width, placeable.height) { /* Do not place */ }
+    }
+
+    private fun ApproachMeasureScope.placeNormally(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val placeable = measurable.measure(constraints)
+        stateInContent.lastSize = placeable.size()
+        return layout(placeable.width, placeable.height) {
+            coordinates?.let {
+                with(layoutImpl.lookaheadScope) {
+                    stateInContent.lastOffset =
+                        lookaheadScopeCoordinates.localPositionOf(it, Offset.Zero)
+                }
+            }
+
+            placeable.place(0, 0)
+        }
     }
 
     private fun Placeable.PlacementScope.place(
-        transition: ContentState.Transition<*>?,
+        elementState: TransitionState,
         placeable: Placeable,
     ) {
         with(layoutImpl.lookaheadScope) {
@@ -322,11 +382,12 @@
                 coordinates ?: error("Element ${element.key} does not have any coordinates")
 
             // No need to place the element in this content if we don't want to draw it anyways.
-            if (!shouldPlaceElement(layoutImpl, content.key, element, transition)) {
+            if (!shouldPlaceElement(layoutImpl, content.key, element, elementState)) {
                 recursivelyClearPlacementValues()
                 return
             }
 
+            val transition = elementState as? TransitionState.Transition
             val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
             val targetOffset =
                 computeValue(
@@ -392,11 +453,15 @@
                         return@placeWithLayer
                     }
 
-                    val transition = elementTransition(layoutImpl, element, currentTransitions)
-                    if (!shouldPlaceElement(layoutImpl, content.key, element, transition)) {
+                    val elementState = elementState(layoutImpl, element, currentTransitionStates)
+                    if (
+                        elementState == null ||
+                            !shouldPlaceElement(layoutImpl, content.key, element, elementState)
+                    ) {
                         return@placeWithLayer
                     }
 
+                    val transition = elementState as? TransitionState.Transition
                     alpha = elementAlpha(layoutImpl, element, transition, stateInContent)
                     compositingStrategy = CompositingStrategy.ModulateAlpha
                 }
@@ -426,7 +491,9 @@
     override fun ContentDrawScope.draw() {
         element.wasDrawnInAnyContent = true
 
-        val transition = elementTransition(layoutImpl, element, currentTransitions)
+        val transition =
+            elementState(layoutImpl, element, currentTransitionStates)
+                as? TransitionState.Transition
         val drawScale = getDrawScale(layoutImpl, element, transition, stateInContent)
         if (drawScale == Scale.Default) {
             drawContent()
@@ -469,21 +536,15 @@
     }
 }
 
-/**
- * The transition that we should consider for [element]. This is the last transition where one of
- * its contents contains the element.
- */
-private fun elementTransition(
+/** The [TransitionState] that we should consider for [element]. */
+private fun elementState(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
-    transitions: List<TransitionState.Transition>,
-): ContentState.Transition<*>? {
-    val transition =
-        transitions.fastLastOrNull { transition ->
-            transition.fromScene in element.stateByContent ||
-                transition.toScene in element.stateByContent
-        }
+    transitionStates: List<TransitionState>,
+): TransitionState? {
+    val state = elementState(transitionStates, isInContent = { it in element.stateByContent })
 
+    val transition = state as? TransitionState.Transition
     val previousTransition = element.lastTransition
     element.lastTransition = transition
 
@@ -498,14 +559,73 @@
         }
     }
 
-    return transition
+    return state
+}
+
+internal inline fun elementState(
+    transitionStates: List<TransitionState>,
+    isInContent: (ContentKey) -> Boolean,
+): TransitionState? {
+    val lastState = transitionStates.last()
+    if (lastState is TransitionState.Idle) {
+        check(transitionStates.size == 1)
+        return lastState
+    }
+
+    // Find the last transition with a content that contains the element.
+    transitionStates.fastForEachReversed { state ->
+        val transition = state as TransitionState.Transition
+        if (isInContent(transition.fromContent) || isInContent(transition.toContent)) {
+            return transition
+        }
+    }
+
+    return null
+}
+
+internal inline fun elementContentWhenIdle(
+    layoutImpl: SceneTransitionLayoutImpl,
+    idle: TransitionState.Idle,
+    isInContent: (ContentKey) -> Boolean,
+): ContentKey {
+    val currentScene = idle.currentScene
+    val overlays = idle.currentOverlays
+    return elementContentWhenIdle(layoutImpl, currentScene, overlays, isInContent)
+}
+
+private inline fun elementContentWhenIdle(
+    layoutImpl: SceneTransitionLayoutImpl,
+    currentScene: SceneKey,
+    overlays: Set<OverlayKey>,
+    isInContent: (ContentKey) -> Boolean,
+): ContentKey {
+    if (overlays.isEmpty()) {
+        return currentScene
+    }
+
+    // Find the overlay with highest zIndex that contains the element.
+    // TODO(b/353679003): Should we cache enabledOverlays into a List<> to avoid a lot of
+    // allocations here?
+    var currentOverlay: OverlayKey? = null
+    for (overlay in overlays) {
+        if (
+            isInContent(overlay) &&
+                (currentOverlay == null ||
+                    (layoutImpl.overlay(overlay).zIndex >
+                        layoutImpl.overlay(currentOverlay).zIndex))
+        ) {
+            currentOverlay = overlay
+        }
+    }
+
+    return currentOverlay ?: currentScene
 }
 
 private fun prepareInterruption(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
-    transition: ContentState.Transition<*>,
-    previousTransition: ContentState.Transition<*>,
+    transition: TransitionState.Transition,
+    previousTransition: TransitionState.Transition,
 ) {
     if (transition.replacedTransition == previousTransition) {
         return
@@ -552,7 +672,7 @@
  */
 private fun reconcileStates(
     element: Element,
-    transition: ContentState.Transition<*>,
+    transition: TransitionState.Transition,
 ) {
     val fromContentState = element.stateByContent[transition.fromContent] ?: return
     val toContentState = element.stateByContent[transition.toContent] ?: return
@@ -621,7 +741,7 @@
  */
 private inline fun <T> computeInterruptedValue(
     layoutImpl: SceneTransitionLayoutImpl,
-    transition: ContentState.Transition<*>?,
+    transition: TransitionState.Transition?,
     value: T,
     unspecifiedValue: T,
     zeroValue: T,
@@ -668,7 +788,7 @@
 private inline fun <T> setPlacementInterruptionDelta(
     element: Element,
     stateInContent: Element.State,
-    transition: ContentState.Transition<*>?,
+    transition: TransitionState.Transition?,
     delta: T,
     setter: (Element.State, T) -> Unit,
 ) {
@@ -694,12 +814,20 @@
     layoutImpl: SceneTransitionLayoutImpl,
     content: ContentKey,
     element: Element,
-    transition: ContentState.Transition<*>?,
+    elementState: TransitionState,
 ): Boolean {
-    // Always place the element if we are idle.
-    if (transition == null) {
-        return true
-    }
+    val transition =
+        when (elementState) {
+            is TransitionState.Idle -> {
+                return content ==
+                    elementContentWhenIdle(
+                        layoutImpl,
+                        elementState,
+                        isInContent = { it in element.stateByContent },
+                    )
+            }
+            is TransitionState.Transition -> elementState
+        }
 
     // Don't place the element in this content if this content is not part of the current element
     // transition.
@@ -725,47 +853,56 @@
         content,
         element.key,
         transition,
+        isInContent = { it in element.stateByContent },
     )
 }
 
-internal fun shouldPlaceOrComposeSharedElement(
+internal inline fun shouldPlaceOrComposeSharedElement(
     layoutImpl: SceneTransitionLayoutImpl,
     content: ContentKey,
     element: ElementKey,
-    transition: ContentState.Transition<*>,
+    transition: TransitionState.Transition,
+    isInContent: (ContentKey) -> Boolean,
 ): Boolean {
-    // If we are overscrolling, only place/compose the element in the overscrolling scene.
-    val overscrollScene = transition.currentOverscrollSpec?.scene
-    if (overscrollScene != null) {
-        return content == overscrollScene
+    val overscrollContent = transition.currentOverscrollSpec?.content
+    if (overscrollContent != null) {
+        return when (transition) {
+            // If we are overscrolling between scenes, only place/compose the element in the
+            // overscrolling scene.
+            is TransitionState.Transition.ChangeScene -> content == overscrollContent
+
+            // If we are overscrolling an overlay, place/compose the element if [content] is the
+            // overscrolling content or if [content] is the current scene and the overscrolling
+            // overlay does not contain the element.
+            is TransitionState.Transition.ReplaceOverlay,
+            is TransitionState.Transition.ShowOrHideOverlay ->
+                content == overscrollContent ||
+                    (content == transition.currentScene && !isInContent(overscrollContent))
+        }
     }
 
     val scenePicker = element.contentPicker
     val pickedScene =
-        when (transition) {
-            is TransitionState.Transition -> {
-                scenePicker.contentDuringTransition(
-                    element = element,
-                    transition = transition,
-                    fromContentZIndex = layoutImpl.scene(transition.fromScene).zIndex,
-                    toContentZIndex = layoutImpl.scene(transition.toScene).zIndex,
-                )
-            }
-        }
+        scenePicker.contentDuringTransition(
+            element = element,
+            transition = transition,
+            fromContentZIndex = layoutImpl.content(transition.fromContent).zIndex,
+            toContentZIndex = layoutImpl.content(transition.toContent).zIndex,
+        )
 
     return pickedScene == content
 }
 
 private fun isSharedElementEnabled(
     element: ElementKey,
-    transition: ContentState.Transition<*>,
+    transition: TransitionState.Transition,
 ): Boolean {
     return sharedElementTransformation(element, transition)?.enabled ?: true
 }
 
 internal fun sharedElementTransformation(
     element: ElementKey,
-    transition: ContentState.Transition<*>,
+    transition: TransitionState.Transition,
 ): SharedElementTransformation? {
     val transformationSpec = transition.transformationSpec
     val sharedInFromContent =
@@ -793,7 +930,7 @@
 private fun isElementOpaque(
     content: Content,
     element: Element,
-    transition: ContentState.Transition<*>?,
+    transition: TransitionState.Transition?,
 ): Boolean {
     if (transition == null) {
         return true
@@ -827,7 +964,7 @@
 private fun elementAlpha(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
-    transition: ContentState.Transition<*>?,
+    transition: TransitionState.Transition?,
     stateInContent: Element.State,
 ): Float {
     val alpha =
@@ -858,7 +995,7 @@
 private fun interruptedAlpha(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
-    transition: ContentState.Transition<*>?,
+    transition: TransitionState.Transition?,
     stateInContent: Element.State,
     alpha: Float,
 ): Float {
@@ -888,7 +1025,7 @@
 private fun measure(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
-    transition: ContentState.Transition<*>?,
+    transition: TransitionState.Transition?,
     stateInContent: Element.State,
     measurable: Measurable,
     constraints: Constraints,
@@ -952,7 +1089,7 @@
 private fun ContentDrawScope.getDrawScale(
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
-    transition: ContentState.Transition<*>?,
+    transition: TransitionState.Transition?,
     stateInContent: Element.State,
 ): Scale {
     val scale =
@@ -1048,7 +1185,7 @@
     layoutImpl: SceneTransitionLayoutImpl,
     currentContentState: Element.State,
     element: Element,
-    transition: ContentState.Transition<*>?,
+    transition: TransitionState.Transition?,
     contentValue: (Element.State) -> T,
     transformation: (ElementTransformations) -> PropertyTransformation<T>?,
     currentValue: () -> T,
@@ -1076,9 +1213,9 @@
     }
 
     val currentContent = currentContentState.content
-    if (transition is ContentState.HasOverscrollProperties) {
+    if (transition is TransitionState.HasOverscrollProperties) {
         val overscroll = transition.currentOverscrollSpec
-        if (overscroll?.scene == currentContent) {
+        if (overscroll?.content == currentContent) {
             val elementSpec =
                 overscroll.transformationSpec.transformations(element.key, currentContent)
             val propertySpec = transformation(elementSpec) ?: return currentValue()
@@ -1104,7 +1241,7 @@
             // TODO(b/290184746): Make sure that we don't overflow transformations associated to a
             // range.
             val directionSign = if (transition.isUpOrLeft) -1 else 1
-            val isToContent = overscroll.scene == transition.toContent
+            val isToContent = overscroll.content == transition.toContent
             val linearProgress = transition.progress.let { if (isToContent) it - 1f else it }
             val progressConverter =
                 overscroll.progressConverter
@@ -1123,17 +1260,30 @@
     // elements follow the finger direction.
     val isSharedElement = fromState != null && toState != null
     if (isSharedElement && isSharedElementEnabled(element.key, transition)) {
-        val start = contentValue(fromState!!)
-        val end = contentValue(toState!!)
+        return interpolateSharedElement(
+            transition = transition,
+            contentValue = contentValue,
+            fromState = fromState!!,
+            toState = toState!!,
+            isSpecified = isSpecified,
+            lerp = lerp,
+        )
+    }
 
-        // TODO(b/316901148): Remove checks to isSpecified() once the lookahead pass runs for all
-        // nodes before the intermediate layout pass.
-        if (!isSpecified(start)) return end
-        if (!isSpecified(end)) return start
-
-        // Make sure we don't read progress if values are the same and we don't need to interpolate,
-        // so we don't invalidate the phase where this is read.
-        return if (start == end) start else lerp(start, end, transition.progress)
+    // If we are replacing an overlay and the element is both in a single overlay and in the current
+    // scene, interpolate the state of the element using the current scene as the other scene.
+    if (!isSharedElement && transition is TransitionState.Transition.ReplaceOverlay) {
+        val currentSceneState = element.stateByContent[transition.currentScene]
+        if (currentSceneState != null) {
+            return interpolateSharedElement(
+                transition = transition,
+                contentValue = contentValue,
+                fromState = fromState ?: currentSceneState,
+                toState = toState ?: currentSceneState,
+                isSpecified = isSpecified,
+                lerp = lerp,
+            )
+        }
     }
 
     // Get the transformed value, i.e. the target value at the beginning (for entering elements) or
@@ -1276,3 +1426,24 @@
         lerp(idleValue, targetValue, rangeProgress)
     }
 }
+
+private inline fun <T> interpolateSharedElement(
+    transition: TransitionState.Transition,
+    contentValue: (Element.State) -> T,
+    fromState: Element.State,
+    toState: Element.State,
+    isSpecified: (T) -> Boolean,
+    lerp: (T, T, Float) -> T
+): T {
+    val start = contentValue(fromState)
+    val end = contentValue(toState)
+
+    // TODO(b/316901148): Remove checks to isSpecified() once the lookahead pass runs for all
+    // nodes before the intermediate layout pass.
+    if (!isSpecified(start)) return end
+    if (!isSpecified(end)) return start
+
+    // Make sure we don't read progress if values are the same and we don't need to interpolate,
+    // so we don't invalidate the phase where this is read.
+    return if (start == end) start else lerp(start, end, transition.progress)
+}
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 bf70ca9..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,
+        interrupted: TransitionState.Transition.ChangeScene,
         newTargetScene: SceneKey,
     ): InterruptionResult?
 }
@@ -76,7 +76,7 @@
  */
 object DefaultInterruptionHandler : InterruptionHandler {
     override fun onInterruption(
-        interrupted: TransitionState.Transition,
+        interrupted: TransitionState.Transition.ChangeScene,
         newTargetScene: SceneKey,
     ): InterruptionResult {
         return InterruptionResult(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index acb436e..ced177c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -63,6 +63,18 @@
     }
 }
 
+/** Key for an overlay. */
+class OverlayKey(
+    debugName: String,
+    identity: Any = Object(),
+) : ContentKey(debugName, identity) {
+    override val testTag: String = "overlay:$debugName"
+
+    override fun toString(): String {
+        return "OverlayKey(debugName=$debugName)"
+    }
+}
+
 /** Key for an element. */
 open class ElementKey(
     debugName: String,
@@ -141,4 +153,15 @@
     override fun toString(): String {
         return "TransitionKey(debugName=$debugName)"
     }
+
+    companion object {
+        /**
+         * A special transition key indicating that the associated transition should be used for
+         * Predictive Back gestures.
+         *
+         * Use this key when defining a transition that you want to be specifically triggered when
+         * the user performs a Predictive Back gesture.
+         */
+        val PredictiveBack = TransitionKey("PredictiveBack")
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index abecdd7..471ad3f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -26,7 +26,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.util.fastLastOrNull
 import com.android.compose.animation.scene.content.Content
 import com.android.compose.animation.scene.content.state.TransitionState
 
@@ -58,6 +57,13 @@
     modifier: Modifier,
     content: @Composable ElementScope<MovableElementContentScope>.() -> Unit,
 ) {
+    check(key.contentPicker.contents.contains(sceneOrOverlay.key)) {
+        val elementName = key.debugName
+        val contentName = sceneOrOverlay.key.debugName
+        "MovableElement $elementName was composed in content $contentName but the " +
+            "MovableElementKey($elementName).contentPicker.contents does not contain $contentName"
+    }
+
     Box(modifier.element(layoutImpl, sceneOrOverlay, key)) {
         val contentScope = sceneOrOverlay.scope
         val boxScope = this
@@ -153,13 +159,20 @@
             // size* as its movable content, i.e. the same *size when idle*. During transitions,
             // this size will be used to interpolate the transition size, during the intermediate
             // layout pass.
+            //
+            // Important: Like in Modifier.element(), we read the transition states during
+            // composition then pass them to Layout to make sure that composition sees new states
+            // before layout and drawing.
+            val transitionStates = layoutImpl.state.transitionStates
             Layout { _, _ ->
                 // No need to measure or place anything.
                 val size =
                     placeholderContentSize(
-                        layoutImpl,
-                        contentKey,
-                        layoutImpl.elements.getValue(element),
+                        layoutImpl = layoutImpl,
+                        content = contentKey,
+                        element = layoutImpl.elements.getValue(element),
+                        elementKey = element,
+                        transitionStates = transitionStates,
                     )
                 layout(size.width, size.height) {}
             }
@@ -172,28 +185,45 @@
     content: ContentKey,
     element: MovableElementKey,
 ): Boolean {
-    val transitions = layoutImpl.state.currentTransitions
-    if (transitions.isEmpty()) {
-        // If we are idle, there is only one [scene] that is composed so we can compose our
-        // movable content here. We still check that [scene] is equal to the current idle scene, to
-        // make sure we only compose it there.
-        return layoutImpl.state.transitionState.currentScene == content
+    return when (
+        val elementState = movableElementState(element, layoutImpl.state.transitionStates)
+    ) {
+        null -> false
+        is TransitionState.Idle ->
+            movableElementContentWhenIdle(layoutImpl, element, elementState) == content
+        is TransitionState.Transition -> {
+            // During transitions, always compose movable elements in the scene picked by their
+            // content picker.
+            val contents = element.contentPicker.contents
+            shouldPlaceOrComposeSharedElement(
+                layoutImpl,
+                content,
+                element,
+                elementState,
+                isInContent = { contents.contains(it) }
+            )
+        }
     }
+}
 
-    // The current transition for this element is the last transition in which either fromScene or
-    // toScene contains the element.
+private fun movableElementState(
+    element: MovableElementKey,
+    transitionStates: List<TransitionState>,
+): TransitionState? {
     val contents = element.contentPicker.contents
-    val transition =
-        transitions.fastLastOrNull { transition ->
-            transition.fromScene in contents || transition.toScene in contents
-        } ?: return false
+    return elementState(transitionStates, isInContent = { contents.contains(it) })
+}
 
-    // Always compose movable elements in the scene picked by their scene picker.
-    return shouldPlaceOrComposeSharedElement(
+private fun movableElementContentWhenIdle(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: MovableElementKey,
+    elementState: TransitionState.Idle,
+): ContentKey {
+    val contents = element.contentPicker.contents
+    return elementContentWhenIdle(
         layoutImpl,
-        content,
-        element,
-        transition,
+        elementState,
+        isInContent = { contents.contains(it) },
     )
 }
 
@@ -205,6 +235,8 @@
     layoutImpl: SceneTransitionLayoutImpl,
     content: ContentKey,
     element: Element,
+    elementKey: MovableElementKey,
+    transitionStates: List<TransitionState>,
 ): IntSize {
     // If the content of the movable element was already composed in this scene before, use that
     // target size.
@@ -213,19 +245,21 @@
         return targetValueInScene
     }
 
-    // This code is only run during transitions (otherwise the content would be composed and the
-    // placeholder would not), so it's ok to cast the state into a Transition directly.
-    val transition = layoutImpl.state.transitionState as TransitionState.Transition
+    // If the element content was already composed in the other overlay/scene, we use that
+    // target size assuming it doesn't change between scenes.
+    // TODO(b/317026105): Provide a way to give a hint size/content for cases where this is
+    // not true.
+    val otherContent =
+        when (val state = movableElementState(elementKey, transitionStates)) {
+            null -> return IntSize.Zero
+            is TransitionState.Idle -> movableElementContentWhenIdle(layoutImpl, elementKey, state)
+            is TransitionState.Transition ->
+                if (state.fromContent == content) state.toContent else state.fromContent
+        }
 
-    // If the content was already composed in the other scene, we use that target size assuming it
-    // doesn't change between scenes.
-    // TODO(b/317026105): Provide a way to give a hint size/content for cases where this is not
-    // true.
-    val otherScene =
-        if (transition.fromScene == content) transition.toScene else transition.fromScene
-    val targetValueInOtherScene = element.stateByContent[otherScene]?.targetSize
-    if (targetValueInOtherScene != null && targetValueInOtherScene != Element.SizeUnspecified) {
-        return targetValueInOtherScene
+    val targetValueInOtherContent = element.stateByContent[otherContent]?.targetSize
+    if (targetValueInOtherContent != null && targetValueInOtherContent != Element.SizeUnspecified) {
+        return targetValueInOtherContent
     }
 
     return IntSize.Zero
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..5780c08 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
@@ -56,7 +56,7 @@
 import com.android.compose.ui.util.SpaceVectorConverter
 import kotlin.coroutines.cancellation.CancellationException
 import kotlin.math.sign
-import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 
@@ -143,8 +143,8 @@
     CompositionLocalConsumerModifierNode,
     ObserverModifierNode,
     SpaceVectorConverter {
-    private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() }
-    private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
+    private val pointerTracker = delegate(SuspendingPointerInputModifierNode { pointerTracker() })
+    private val pointerInput = delegate(SuspendingPointerInputModifierNode { pointerInput() })
     private val velocityTracker = VelocityTracker()
     private var previousEnabled: Boolean = false
 
@@ -153,7 +153,7 @@
             // Reset the pointer input whenever enabled changed.
             if (value != field) {
                 field = value
-                delegate.resetPointerInputHandler()
+                pointerInput.resetPointerInputHandler()
             }
         }
 
@@ -173,7 +173,7 @@
             if (value != field) {
                 field = value
                 converter = SpaceVectorConverter(value)
-                delegate.resetPointerInputHandler()
+                pointerInput.resetPointerInputHandler()
             }
         }
 
@@ -186,19 +186,26 @@
         observeReads {
             val newEnabled = enabled()
             if (newEnabled != previousEnabled) {
-                delegate.resetPointerInputHandler()
+                pointerInput.resetPointerInputHandler()
             }
             previousEnabled = newEnabled
         }
     }
 
-    override fun onCancelPointerInput() = delegate.onCancelPointerInput()
+    override fun onCancelPointerInput() {
+        pointerTracker.onCancelPointerInput()
+        pointerInput.onCancelPointerInput()
+    }
 
     override fun onPointerEvent(
         pointerEvent: PointerEvent,
         pass: PointerEventPass,
         bounds: IntSize
-    ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
+    ) {
+        // The order is important here: the tracker is always called first.
+        pointerTracker.onPointerEvent(pointerEvent, pass, bounds)
+        pointerInput.onPointerEvent(pointerEvent, pass, bounds)
+    }
 
     private var startedPosition: Offset? = null
     private var pointersDown: Int = 0
@@ -211,81 +218,77 @@
         )
     }
 
+    private suspend fun PointerInputScope.pointerTracker() {
+        val currentContext = currentCoroutineContext()
+        awaitPointerEventScope {
+            // Intercepts pointer inputs and exposes [PointersInfo], via
+            // [requireAncestorPointersInfoOwner], to our descendants.
+            while (currentContext.isActive) {
+                // During the Initial pass, we receive the event after our ancestors.
+                val pointers = awaitPointerEvent(PointerEventPass.Initial).changes
+                pointersDown = pointers.countDown()
+                if (pointersDown == 0) {
+                    // There are no more pointers down
+                    startedPosition = null
+                } else if (startedPosition == null) {
+                    startedPosition = pointers.first().position
+                    if (enabled()) {
+                        onFirstPointerDown()
+                    }
+                }
+            }
+        }
+    }
+
     private suspend fun PointerInputScope.pointerInput() {
         if (!enabled()) {
             return
         }
 
-        coroutineScope {
-            launch {
-                // Intercepts pointer inputs and exposes [PointersInfo], via
-                // [requireAncestorPointersInfoOwner], to our descendants.
-                awaitPointerEventScope {
-                    while (isActive) {
-                        // During the Initial pass, we receive the event after our ancestors.
-                        val pointers = awaitPointerEvent(PointerEventPass.Initial).changes
-
-                        pointersDown = pointers.countDown()
-                        if (pointersDown == 0) {
-                            // There are no more pointers down
-                            startedPosition = null
-                        } else if (startedPosition == null) {
-                            startedPosition = pointers.first().position
-                            onFirstPointerDown()
-                        }
-                    }
-                }
-            }
-
-            // The order is important here: we want to make sure that the previous PointerEventScope
-            // is initialized first. This ensures that the following PointerEventScope doesn't
-            // receive more events than the first one.
-            launch {
-                awaitPointerEventScope {
-                    while (isActive) {
-                        try {
-                            detectDragGestures(
-                                orientation = orientation,
-                                startDragImmediately = startDragImmediately,
-                                onDragStart = { startedPosition, overSlop, pointersDown ->
-                                    velocityTracker.resetTracking()
-                                    onDragStarted(startedPosition, overSlop, pointersDown)
-                                },
-                                onDrag = { controller, change, amount ->
-                                    velocityTracker.addPointerInputChange(change)
-                                    dispatchScrollEvents(
-                                        availableOnPreScroll = amount,
-                                        onScroll = { controller.onDrag(it) },
-                                        source = NestedScrollSource.UserInput,
-                                    )
-                                },
-                                onDragEnd = { controller ->
-                                    startFlingGesture(
-                                        initialVelocity =
-                                            currentValueOf(LocalViewConfiguration)
-                                                .maximumFlingVelocity
-                                                .let {
-                                                    val maxVelocity = Velocity(it, it)
-                                                    velocityTracker.calculateVelocity(maxVelocity)
-                                                }
-                                                .toFloat(),
-                                        onFling = { controller.onStop(it, canChangeScene = true) }
-                                    )
-                                },
-                                onDragCancel = { controller ->
-                                    startFlingGesture(
-                                        initialVelocity = 0f,
-                                        onFling = { controller.onStop(it, canChangeScene = true) }
-                                    )
-                                },
-                                swipeDetector = swipeDetector,
+        val currentContext = currentCoroutineContext()
+        awaitPointerEventScope {
+            while (currentContext.isActive) {
+                try {
+                    detectDragGestures(
+                        orientation = orientation,
+                        startDragImmediately = startDragImmediately,
+                        onDragStart = { startedPosition, overSlop, pointersDown ->
+                            velocityTracker.resetTracking()
+                            onDragStarted(startedPosition, overSlop, pointersDown)
+                        },
+                        onDrag = { controller, change, amount ->
+                            velocityTracker.addPointerInputChange(change)
+                            dispatchScrollEvents(
+                                availableOnPreScroll = amount,
+                                onScroll = { controller.onDrag(it) },
+                                source = NestedScrollSource.UserInput,
                             )
-                        } catch (exception: CancellationException) {
-                            // If the coroutine scope is active, we can just restart the drag cycle.
-                            if (!isActive) {
-                                throw exception
-                            }
-                        }
+                        },
+                        onDragEnd = { controller ->
+                            startFlingGesture(
+                                initialVelocity =
+                                    currentValueOf(LocalViewConfiguration)
+                                        .maximumFlingVelocity
+                                        .let {
+                                            val maxVelocity = Velocity(it, it)
+                                            velocityTracker.calculateVelocity(maxVelocity)
+                                        }
+                                        .toFloat(),
+                                onFling = { controller.onStop(it, canChangeContent = true) }
+                            )
+                        },
+                        onDragCancel = { controller ->
+                            startFlingGesture(
+                                initialVelocity = 0f,
+                                onFling = { controller.onStop(it, canChangeContent = true) }
+                            )
+                        },
+                        swipeDetector = swipeDetector,
+                    )
+                } catch (exception: CancellationException) {
+                    // If the coroutine scope is active, we can just restart the drag cycle.
+                    if (!currentContext.isActive) {
+                        throw exception
                     }
                 }
             }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 945043d..8a0e462 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -35,13 +35,6 @@
  */
 enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) {
     /**
-     * During scene transitions, if we are within
-     * [SceneTransitionLayoutImpl.transitionInterceptionThreshold], the [SceneTransitionLayout]
-     * consumes scroll events instead of the scrollable component.
-     */
-    DuringTransitionBetweenScenes(canStartOnPostFling = false),
-
-    /**
      * Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the
      * gesture begins at the edge of the scrollable component (so that a scroll in that direction
      * can no longer be consumed). If the gesture is partially consumed by the scrollable component,
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 8f1a4141..bd21a69 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
@@ -31,9 +31,6 @@
  *    layout or Compose drawing phases.
  * 2. [ObservableTransitionState] values are backed by Kotlin [Flow]s and can be collected by
  *    non-Compose code to observe value changes.
- * 3. [ObservableTransitionState.Transition.fromScene] and
- *    [ObservableTransitionState.Transition.toScene] will never be equal, while
- *    [TransitionState.Transition.fromScene] and [TransitionState.Transition.toScene] can be equal.
  */
 sealed interface ObservableTransitionState {
     /**
@@ -43,7 +40,9 @@
     fun currentScene(): Flow<SceneKey> {
         return when (this) {
             is Idle -> flowOf(currentScene)
-            is Transition -> currentScene
+            is Transition.ChangeScene -> currentScene
+            is Transition.ShowOrHideOverlay -> flowOf(currentScene)
+            is Transition.ReplaceOverlay -> flowOf(currentScene)
         }
     }
 
@@ -51,10 +50,10 @@
     data class Idle(val currentScene: SceneKey) : ObservableTransitionState
 
     /** There is a transition animating between two scenes. */
-    class Transition(
-        val fromScene: SceneKey,
-        val toScene: SceneKey,
-        val currentScene: Flow<SceneKey>,
+    sealed class Transition(
+        val fromContent: ContentKey,
+        val toContent: ContentKey,
+        val currentOverlays: Flow<Set<OverlayKey>>,
         val progress: Flow<Float>,
 
         /**
@@ -76,29 +75,125 @@
         val isUserInputOngoing: Flow<Boolean>,
 
         /** Current progress of the preview part of the transition */
-        val previewProgress: Flow<Float> = flowOf(0f),
+        val previewProgress: Flow<Float>,
 
         /** Whether the transition is currently in the preview stage or not */
-        val isInPreviewStage: Flow<Boolean> = flowOf(false),
+        val isInPreviewStage: Flow<Boolean>,
     ) : ObservableTransitionState {
         override fun toString(): String =
             """Transition
-                |(from=$fromScene,
-                | to=$toScene,
+                |(from=$fromContent,
+                | to=$toContent,
                 | isInitiatedByUserInput=$isInitiatedByUserInput,
                 | isUserInputOngoing=$isUserInputOngoing
                 |)"""
                 .trimMargin()
+
+        /** A transition animating between [fromScene] and [toScene]. */
+        class ChangeScene(
+            val fromScene: SceneKey,
+            val toScene: SceneKey,
+            val currentScene: Flow<SceneKey>,
+            currentOverlays: Flow<Set<OverlayKey>>,
+            progress: Flow<Float>,
+            isInitiatedByUserInput: Boolean,
+            isUserInputOngoing: Flow<Boolean>,
+            previewProgress: Flow<Float>,
+            isInPreviewStage: Flow<Boolean>,
+        ) :
+            Transition(
+                fromScene,
+                toScene,
+                currentOverlays,
+                progress,
+                isInitiatedByUserInput,
+                isUserInputOngoing,
+                previewProgress,
+                isInPreviewStage,
+            )
+
+        /** The [overlay] is either showing from [currentScene] or hiding into [currentScene]. */
+        class ShowOrHideOverlay(
+            val overlay: OverlayKey,
+            fromContent: ContentKey,
+            toContent: ContentKey,
+            val currentScene: SceneKey,
+            currentOverlays: Flow<Set<OverlayKey>>,
+            progress: Flow<Float>,
+            isInitiatedByUserInput: Boolean,
+            isUserInputOngoing: Flow<Boolean>,
+            previewProgress: Flow<Float>,
+            isInPreviewStage: Flow<Boolean>,
+        ) :
+            Transition(
+                fromContent,
+                toContent,
+                currentOverlays,
+                progress,
+                isInitiatedByUserInput,
+                isUserInputOngoing,
+                previewProgress,
+                isInPreviewStage,
+            )
+
+        /** We are transitioning from [fromOverlay] to [toOverlay]. */
+        class ReplaceOverlay(
+            val fromOverlay: OverlayKey,
+            val toOverlay: OverlayKey,
+            val currentScene: SceneKey,
+            currentOverlays: Flow<Set<OverlayKey>>,
+            progress: Flow<Float>,
+            isInitiatedByUserInput: Boolean,
+            isUserInputOngoing: Flow<Boolean>,
+            previewProgress: Flow<Float>,
+            isInPreviewStage: Flow<Boolean>,
+        ) :
+            Transition(
+                fromOverlay,
+                toOverlay,
+                currentOverlays,
+                progress,
+                isInitiatedByUserInput,
+                isUserInputOngoing,
+                previewProgress,
+                isInPreviewStage,
+            )
+
+        companion object {
+            operator fun invoke(
+                fromScene: SceneKey,
+                toScene: SceneKey,
+                currentScene: Flow<SceneKey>,
+                progress: Flow<Float>,
+                isInitiatedByUserInput: Boolean,
+                isUserInputOngoing: Flow<Boolean>,
+                previewProgress: Flow<Float> = flowOf(0f),
+                isInPreviewStage: Flow<Boolean> = flowOf(false),
+                currentOverlays: Flow<Set<OverlayKey>> = flowOf(emptySet()),
+            ): ChangeScene {
+                return ChangeScene(
+                    fromScene,
+                    toScene,
+                    currentScene,
+                    currentOverlays,
+                    progress,
+                    isInitiatedByUserInput,
+                    isUserInputOngoing,
+                    previewProgress,
+                    isInPreviewStage,
+                )
+            }
+        }
     }
 
-    fun isIdle(scene: SceneKey?): Boolean {
+    fun isIdle(scene: SceneKey? = null): Boolean {
         return this is Idle && (scene == null || this.currentScene == scene)
     }
 
-    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
+    fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean {
         return this is Transition &&
-            (from == null || this.fromScene == from) &&
-            (to == null || this.toScene == to)
+            (from == null || this.fromContent == from) &&
+            (to == null || this.toContent == to)
     }
 }
 
@@ -111,16 +206,45 @@
     return snapshotFlow {
             when (val state = transitionState) {
                 is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene)
-                is TransitionState.Transition -> {
-                    ObservableTransitionState.Transition(
+                is TransitionState.Transition.ChangeScene -> {
+                    ObservableTransitionState.Transition.ChangeScene(
                         fromScene = state.fromScene,
                         toScene = state.toScene,
                         currentScene = snapshotFlow { state.currentScene },
+                        currentOverlays = flowOf(state.currentOverlays),
                         progress = snapshotFlow { state.progress },
                         isInitiatedByUserInput = state.isInitiatedByUserInput,
                         isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
                         previewProgress = snapshotFlow { state.previewProgress },
-                        isInPreviewStage = snapshotFlow { state.isInPreviewStage }
+                        isInPreviewStage = snapshotFlow { state.isInPreviewStage },
+                    )
+                }
+                is TransitionState.Transition.ShowOrHideOverlay -> {
+                    check(state.fromOrToScene == state.currentScene)
+                    ObservableTransitionState.Transition.ShowOrHideOverlay(
+                        overlay = state.overlay,
+                        fromContent = state.fromContent,
+                        toContent = state.toContent,
+                        currentScene = state.currentScene,
+                        currentOverlays = snapshotFlow { state.currentOverlays },
+                        progress = snapshotFlow { state.progress },
+                        isInitiatedByUserInput = state.isInitiatedByUserInput,
+                        isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+                        previewProgress = snapshotFlow { state.previewProgress },
+                        isInPreviewStage = snapshotFlow { state.isInPreviewStage },
+                    )
+                }
+                is TransitionState.Transition.ReplaceOverlay -> {
+                    ObservableTransitionState.Transition.ReplaceOverlay(
+                        fromOverlay = state.fromOverlay,
+                        toOverlay = state.toOverlay,
+                        currentScene = state.currentScene,
+                        currentOverlays = snapshotFlow { state.currentOverlays },
+                        progress = snapshotFlow { state.progress },
+                        isInitiatedByUserInput = state.isInitiatedByUserInput,
+                        isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+                        previewProgress = snapshotFlow { state.previewProgress },
+                        isInPreviewStage = snapshotFlow { state.isInPreviewStage },
                     )
                 }
             }
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 cc53a28..8480d3a 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,71 @@
 
 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.snap
+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 com.android.compose.animation.scene.UserActionResult.ChangeScene
+import com.android.compose.animation.scene.UserActionResult.HideOverlay
+import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
+import com.android.compose.animation.scene.UserActionResult.ShowOverlay
+import com.android.compose.animation.scene.transition.animateProgress
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.map
 
 @Composable
 internal fun PredictiveBackHandler(
-    state: MutableSceneTransitionLayoutStateImpl,
-    coroutineScope: CoroutineScope,
-    targetSceneForBack: SceneKey? = null,
+    layoutImpl: SceneTransitionLayoutImpl,
+    result: UserActionResult?,
 ) {
     PredictiveBackHandler(
-        enabled = targetSceneForBack != null,
-    ) { progress: Flow<BackEventCompat> ->
-        val fromScene = state.transitionState.currentScene
-        if (targetSceneForBack == null || targetSceneForBack == fromScene) {
+        enabled = result != null,
+    ) { events: Flow<BackEventCompat> ->
+        if (result == null) {
             // Note: We have to collect progress otherwise PredictiveBackHandler will throw.
-            progress.first()
+            events.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,
+                if (result.transitionKey != null) {
+                    result
+                } else {
+                    result.copy(transitionKey = TransitionKey.PredictiveBack)
+                },
+                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)
-        }
+        animateProgress(
+            state = layoutImpl.state,
+            animation = animation,
+            progress = events.map { it.progress },
+
+            // Use the transformationSpec.progressSpec. We will lazily access it later once the
+            // transition has been started, because at this point the transformation spec of the
+            // transition is not computed yet.
+            commitSpec = null,
+
+            // The predictive back APIs will automatically animate the progress for us in this case
+            // so there is no need to animate it.
+            cancelSpec = snap(),
+        )
     }
 }
 
-private class PredictiveBackTransition(
-    val state: MutableSceneTransitionLayoutStateImpl,
-    val coroutineScope: CoroutineScope,
-    fromScene: SceneKey,
-    toScene: SceneKey,
-) : TransitionState.Transition(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
-        }
-
-        if (scene != currentScene && state.transitionState == this && state.canChangeScene(scene)) {
-            currentScene = scene
-        }
-
-        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 }
-
-        // 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 }
+private fun UserActionResult.copy(
+    transitionKey: TransitionKey? = this.transitionKey
+): UserActionResult {
+    return when (this) {
+        is ChangeScene -> copy(transitionKey = transitionKey)
+        is ShowOverlay -> copy(transitionKey = transitionKey)
+        is HideOverlay -> copy(transitionKey = transitionKey)
+        is ReplaceByOverlay -> copy(transitionKey = transitionKey)
     }
 }
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 65a7367..061613f 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
@@ -49,7 +49,7 @@
  *   if any.
  * @param transitionInterceptionThreshold used during a scene transition. For the scene to be
  *   intercepted, the progress value must be above the threshold, and below (1 - threshold).
- * @param scenes the configuration of the different scenes of this layout.
+ * @param builder the configuration of the different scenes and overlays of this layout.
  */
 @Composable
 fun SceneTransitionLayout(
@@ -58,7 +58,7 @@
     swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0.05f,
-    scenes: SceneTransitionLayoutScope.() -> Unit,
+    builder: SceneTransitionLayoutScope.() -> Unit,
 ) {
     SceneTransitionLayoutForTesting(
         state,
@@ -67,7 +67,7 @@
         swipeDetector,
         transitionInterceptionThreshold,
         onLayoutImpl = null,
-        scenes,
+        builder,
     )
 }
 
@@ -86,6 +86,31 @@
         userActions: Map<UserAction, UserActionResult> = emptyMap(),
         content: @Composable ContentScope.() -> Unit,
     )
+
+    /**
+     * Add an overlay to this layout, identified by [key].
+     *
+     * Overlays are displayed above scenes and can be toggled using
+     * [MutableSceneTransitionLayoutState.showOverlay] and
+     * [MutableSceneTransitionLayoutState.hideOverlay].
+     *
+     * Overlays will have a maximum size that is the size of the layout without overlays, i.e. an
+     * overlay can be fillMaxSize() to match the layout size but it won't make the layout bigger.
+     *
+     * By default overlays are centered in their layout but they can be aligned differently using
+     * [alignment].
+     *
+     * Important: overlays must be defined after all scenes. Overlay order along the z-axis follows
+     * call order. Calling overlay(A) followed by overlay(B) will mean that overlay B renders
+     * after/above overlay A.
+     */
+    fun overlay(
+        key: OverlayKey,
+        userActions: Map<UserAction, UserActionResult> =
+            mapOf(Back to UserActionResult.HideOverlay(key)),
+        alignment: Alignment = Alignment.Center,
+        content: @Composable ContentScope.() -> Unit,
+    )
 }
 
 /**
@@ -239,7 +264,7 @@
     /**
      * Animate some value at the content level.
      *
-     * @param value the value of this shared value in the current scene.
+     * @param value the value of this shared value in the current content.
      * @param key the key of this shared value.
      * @param type the [SharedValueType] of this animated value.
      * @param canOverflow whether this value can overflow past the values it is interpolated
@@ -292,7 +317,7 @@
     /**
      * Animate some value associated to this element.
      *
-     * @param value the value of this shared value in the current scene.
+     * @param value the value of this shared value in the current content.
      * @param key the key of this shared value.
      * @param type the [SharedValueType] of this animated value.
      * @param canOverflow whether this value can overflow past the values it is interpolated
@@ -454,20 +479,95 @@
 }
 
 /** 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]. */
+    data class ShowOverlay(
+        val overlay: OverlayKey,
+        override val transitionKey: TransitionKey? = null,
+        override val requiresFullDistanceSwipe: Boolean = false,
+    ) : UserActionResult(transitionKey, requiresFullDistanceSwipe) {
+        override fun toContent(currentScene: SceneKey): ContentKey = overlay
+    }
+
+    /** A [UserActionResult] that hides [overlay]. */
+    data class HideOverlay(
+        val overlay: OverlayKey,
+        override val transitionKey: TransitionKey? = null,
+        override val 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.
+     */
+    data class ReplaceByOverlay(
+        val overlay: OverlayKey,
+        override val transitionKey: TransitionKey? = null,
+        override val 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)
+
+        /** A [UserActionResult] that shows [toOverlay]. */
+        operator fun invoke(
+            /** The overlay we should be transitioning to during the [UserAction]. */
+            toOverlay: OverlayKey,
+
+            /** 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 = ShowOverlay(toOverlay, transitionKey, requiresFullDistanceSwipe)
+    }
+}
 
 fun interface UserActionDistance {
     /**
@@ -509,11 +609,11 @@
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     transitionInterceptionThreshold: Float = 0f,
     onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
-    scenes: SceneTransitionLayoutScope.() -> Unit,
+    builder: SceneTransitionLayoutScope.() -> Unit,
 ) {
     val density = LocalDensity.current
     val layoutDirection = LocalLayoutDirection.current
-    val coroutineScope = rememberCoroutineScope()
+    val animationScope = rememberCoroutineScope()
     val layoutImpl = remember {
         SceneTransitionLayoutImpl(
                 state = state as MutableSceneTransitionLayoutStateImpl,
@@ -521,15 +621,15 @@
                 layoutDirection = layoutDirection,
                 swipeSourceDetector = swipeSourceDetector,
                 transitionInterceptionThreshold = transitionInterceptionThreshold,
-                builder = scenes,
-                coroutineScope = coroutineScope,
+                builder = builder,
+                animationScope = animationScope,
             )
             .also { onLayoutImpl?.invoke(it) }
     }
 
     // TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
     // SnapshotStateMap anymore.
-    layoutImpl.updateScenes(scenes, layoutDirection)
+    layoutImpl.updateContents(builder, layoutDirection)
 
     SideEffect {
         if (state != layoutImpl.state) {
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 062d553..f36c0fa 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,12 +16,15 @@
 
 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
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.key
 import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ApproachLayoutModifierNode
@@ -29,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
@@ -36,8 +40,11 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachReversed
+import androidx.compose.ui.zIndex
 import com.android.compose.animation.scene.content.Content
+import com.android.compose.animation.scene.content.Overlay
 import com.android.compose.animation.scene.content.Scene
+import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.ui.util.lerp
 import kotlinx.coroutines.CoroutineScope
 
@@ -52,14 +59,30 @@
     internal var swipeSourceDetector: SwipeSourceDetector,
     internal var transitionInterceptionThreshold: Float,
     builder: SceneTransitionLayoutScope.() -> Unit,
-    internal val coroutineScope: CoroutineScope,
+
+    /**
+     * The scope that should be used by *animations started by this layout only*, i.e. animations
+     * triggered by gestures set up on this layout in [swipeToScene] or interruption decay
+     * animations.
+     */
+    internal val animationScope: CoroutineScope,
 ) {
     /**
      * The map of [Scene]s.
      *
      * TODO(b/317014852): Make this a normal MutableMap instead.
      */
-    internal val scenes = SnapshotStateMap<SceneKey, Scene>()
+    private val scenes = SnapshotStateMap<SceneKey, Scene>()
+
+    /**
+     * The map of [Overlays].
+     *
+     * Note: We lazily create this map to avoid instantiation an expensive SnapshotStateMap in the
+     * common case where there is no overlay in this layout.
+     */
+    private var _overlays: MutableMap<OverlayKey, Overlay>? = null
+    private val overlays
+        get() = _overlays ?: SnapshotStateMap<OverlayKey, Overlay>().also { _overlays = it }
 
     /**
      * The map of [Element]s.
@@ -117,24 +140,18 @@
     internal lateinit var lookaheadScope: LookaheadScope
         private set
 
+    internal var lastSize: IntSize = IntSize.Zero
+
     init {
-        updateScenes(builder, layoutDirection)
+        updateContents(builder, layoutDirection)
 
         // DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
         // current scene (required for SwipeTransition).
         horizontalDraggableHandler =
-            DraggableHandlerImpl(
-                layoutImpl = this,
-                orientation = Orientation.Horizontal,
-                coroutineScope = coroutineScope,
-            )
+            DraggableHandlerImpl(layoutImpl = this, orientation = Orientation.Horizontal)
 
         verticalDraggableHandler =
-            DraggableHandlerImpl(
-                layoutImpl = this,
-                orientation = Orientation.Vertical,
-                coroutineScope = coroutineScope,
-            )
+            DraggableHandlerImpl(layoutImpl = this, orientation = Orientation.Vertical)
 
         // Make sure that the state is created on the same thread (most probably the main thread)
         // than this STLImpl.
@@ -151,22 +168,54 @@
         return scenes[key] ?: error("Scene $key is not configured")
     }
 
+    internal fun sceneOrNull(key: SceneKey): Scene? = scenes[key]
+
+    internal fun overlay(key: OverlayKey): Overlay {
+        return overlays[key] ?: error("Overlay $key is not configured")
+    }
+
     internal fun content(key: ContentKey): Content {
         return when (key) {
             is SceneKey -> scene(key)
+            is OverlayKey -> overlay(key)
         }
     }
 
-    internal fun updateScenes(
+    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,
     ) {
-        // Keep a reference of the current scenes. After processing [builder], the scenes that were
-        // not configured will be removed.
+        // Keep a reference of the current contents. After processing [builder], the contents that
+        // were not configured will be removed.
         val scenesToRemove = scenes.keys.toMutableSet()
+        val overlaysToRemove =
+            if (_overlays == null) mutableSetOf() else overlays.keys.toMutableSet()
 
         // The incrementing zIndex of each scene.
         var zIndex = 0f
+        var overlaysDefined = false
 
         object : SceneTransitionLayoutScope {
                 override fun scene(
@@ -174,10 +223,11 @@
                     userActions: Map<UserAction, UserActionResult>,
                     content: @Composable ContentScope.() -> Unit,
                 ) {
+                    require(!overlaysDefined) { "all scenes must be defined before overlays" }
+
                     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.
@@ -198,10 +248,84 @@
 
                     zIndex++
                 }
+
+                override fun overlay(
+                    key: OverlayKey,
+                    userActions: Map<UserAction, UserActionResult>,
+                    alignment: Alignment,
+                    content: @Composable (ContentScope.() -> Unit)
+                ) {
+                    overlaysDefined = true
+                    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.
+                        overlays[key] =
+                            Overlay(
+                                key,
+                                this@SceneTransitionLayoutImpl,
+                                content,
+                                resolvedUserActions,
+                                zIndex,
+                                alignment,
+                            )
+                    }
+
+                    zIndex++
+                }
             }
             .builder()
 
         scenesToRemove.forEach { scenes.remove(it) }
+        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
@@ -219,17 +343,21 @@
                 lookaheadScope = this
 
                 BackHandler()
-
-                scenesToCompose().fastForEach { scene -> key(scene.key) { scene.Content() } }
+                Scenes()
+                Overlays()
             }
         }
     }
 
     @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
+    private fun Scenes() {
+        scenesToCompose().fastForEach { scene -> key(scene.key) { scene.Content() } }
     }
 
     private fun scenesToCompose(): List<Scene> {
@@ -247,16 +375,81 @@
 
                 // Compose the new scene we are going to first.
                 transitions.fastForEachReversed { transition ->
-                    maybeAdd(transition.toScene)
-                    maybeAdd(transition.fromScene)
+                    when (transition) {
+                        is TransitionState.Transition.ChangeScene -> {
+                            maybeAdd(transition.toScene)
+                            maybeAdd(transition.fromScene)
+                        }
+                        is TransitionState.Transition.ShowOrHideOverlay ->
+                            maybeAdd(transition.fromOrToScene)
+                        is TransitionState.Transition.ReplaceOverlay -> {}
+                    }
                 }
+
+                // Make sure that the current scene is always composed.
+                maybeAdd(transitions.last().currentScene)
             }
         }
     }
 
-    internal fun setScenesTargetSizeForTest(size: IntSize) {
-        scenes.values.forEach { it.targetSize = size }
+    @Composable
+    private fun BoxScope.Overlays() {
+        val overlaysOrderedByZIndex = overlaysToComposeOrderedByZIndex()
+        if (overlaysOrderedByZIndex.isEmpty()) {
+            return
+        }
+
+        // We put the overlays inside a Box that is matching the layout size so that overlays are
+        // measured after all scenes and that their max size is the size of the layout without the
+        // overlays.
+        Box(Modifier.matchParentSize().zIndex(overlaysOrderedByZIndex.first().zIndex)) {
+            overlaysOrderedByZIndex.fastForEach { overlay ->
+                key(overlay.key) { overlay.Content(Modifier.align(overlay.alignment)) }
+            }
+        }
     }
+
+    private fun overlaysToComposeOrderedByZIndex(): List<Overlay> {
+        if (_overlays == null) return emptyList()
+
+        val transitions = state.currentTransitions
+        return if (transitions.isEmpty()) {
+                state.transitionState.currentOverlays.map { overlay(it) }
+            } else {
+                buildList {
+                    val visited = mutableSetOf<OverlayKey>()
+                    fun maybeAdd(key: OverlayKey) {
+                        if (visited.add(key)) {
+                            add(overlay(key))
+                        }
+                    }
+
+                    transitions.fastForEach { transition ->
+                        when (transition) {
+                            is TransitionState.Transition.ChangeScene -> {}
+                            is TransitionState.Transition.ShowOrHideOverlay ->
+                                maybeAdd(transition.overlay)
+                            is TransitionState.Transition.ReplaceOverlay -> {
+                                maybeAdd(transition.fromOverlay)
+                                maybeAdd(transition.toOverlay)
+                            }
+                        }
+                    }
+
+                    // Make sure that all current overlays are composed.
+                    transitions.last().currentOverlays.forEach { maybeAdd(it) }
+                }
+            }
+            .sortedBy { it.zIndex }
+    }
+
+    @VisibleForTesting
+    internal fun setContentsAndLayoutTargetSizeForTest(size: IntSize) {
+        lastSize = size
+        (scenes.values + overlays.values).forEach { it.targetSize = size }
+    }
+
+    internal fun overlaysOrNullForTest(): Map<OverlayKey, Overlay>? = _overlays
 }
 
 private data class LayoutElement(private val layoutImpl: SceneTransitionLayoutImpl) :
@@ -269,7 +462,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()
     }
@@ -284,7 +481,8 @@
 
         val width: Int
         val height: Int
-        val transition = layoutImpl.state.currentTransition
+        val transition =
+            layoutImpl.state.currentTransition as? TransitionState.Transition.ChangeScene
         if (transition == null) {
             width = placeable.width
             height = placeable.height
@@ -304,7 +502,7 @@
                 val progress =
                     when {
                         overscrollSpec == null -> transition.progress
-                        overscrollSpec.scene == transition.toScene -> 1f
+                        overscrollSpec.content == transition.toScene -> 1f
                         else -> 0f
                     }
 
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 44f5964f..c2d5dd05 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
@@ -25,12 +25,15 @@
 import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastForEach
-import com.android.compose.animation.scene.content.state.ContentState
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.transition.link.LinkedTransition
 import com.android.compose.animation.scene.transition.link.StateLink
 import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
 /**
  * The state of a [SceneTransitionLayout].
@@ -40,6 +43,21 @@
 @Stable
 sealed interface SceneTransitionLayoutState {
     /**
+     * The current effective scene. If a new transition is triggered, it will start from this scene.
+     */
+    val currentScene: SceneKey
+
+    /**
+     * The current set of overlays. This represents the set of overlays that will be visible on
+     * screen once all [currentTransitions] are finished.
+     *
+     * @see MutableSceneTransitionLayoutState.showOverlay
+     * @see MutableSceneTransitionLayoutState.hideOverlay
+     * @see MutableSceneTransitionLayoutState.replaceOverlay
+     */
+    val currentOverlays: Set<OverlayKey>
+
+    /**
      * The current [TransitionState]. All values read here are backed by the Snapshot system.
      *
      * To observe those values outside of Compose/the Snapshot system, use
@@ -67,12 +85,15 @@
 
     /**
      * Whether we are transitioning. If [from] or [to] is empty, we will also check that they match
-     * the scenes we are animating from and/or to.
+     * the contents we are animating from and/or to.
      */
-    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean
+    fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean
 
-    /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
-    fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean
+    /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
+    fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean
+
+    /** Whether we are transitioning from or to [content]. */
+    fun isTransitioningFromOrTo(content: ContentKey): Boolean
 }
 
 /** A [SceneTransitionLayoutState] whose target scene can be imperatively set. */
@@ -91,27 +112,70 @@
      * If [targetScene] is different than the [currentScene][TransitionState.currentScene] of
      * [transitionState], then this will animate to [targetScene]. The associated
      * [TransitionState.Transition] will be returned and will be set as the current
-     * [transitionState] of this [MutableSceneTransitionLayoutState].
+     * [transitionState] of this [MutableSceneTransitionLayoutState]. The [Job] in which the
+     * transition runs will be returned, allowing you to easily [join][Job.join] or
+     * [cancel][Job.cancel] the animation.
      *
      * Note that because a non-null [TransitionState.Transition] is returned does not mean that the
      * transition will finish and that we will settle to [targetScene]. The returned transition
      * might still be interrupted, for instance by another call to [setTargetScene] or by a user
      * gesture.
      *
-     * If [this] [CoroutineScope] is cancelled during the transition and that the transition was
-     * still active, then the [transitionState] of this [MutableSceneTransitionLayoutState] will be
-     * set to `TransitionState.Idle(targetScene)`.
-     *
-     * TODO(b/318794193): Add APIs to await() and cancel() any [TransitionState.Transition].
+     * If [animationScope] is cancelled during the transition and that the transition was still
+     * active, then the [transitionState] of this [MutableSceneTransitionLayoutState] will be set to
+     * `TransitionState.Idle(targetScene)`.
      */
     fun setTargetScene(
         targetScene: SceneKey,
-        coroutineScope: CoroutineScope,
+        animationScope: CoroutineScope,
         transitionKey: TransitionKey? = null,
-    ): TransitionState.Transition?
+    ): Pair<TransitionState.Transition, Job>?
 
     /** Immediately snap to the given [scene]. */
-    fun snapToScene(scene: SceneKey)
+    fun snapToScene(
+        scene: SceneKey,
+        currentOverlays: Set<OverlayKey> = transitionState.currentOverlays,
+    )
+
+    /**
+     * 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 in [currentOverlays].
+     */
+    fun showOverlay(
+        overlay: OverlayKey,
+        animationScope: CoroutineScope,
+        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 not in [currentOverlays].
+     */
+    fun hideOverlay(
+        overlay: OverlayKey,
+        animationScope: CoroutineScope,
+        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 in [currentOverlays] or if [to] is already in
+     * [currentOverlays].
+     */
+    fun replaceOverlay(
+        from: OverlayKey,
+        to: OverlayKey,
+        animationScope: CoroutineScope,
+        transitionKey: TransitionKey? = null,
+    )
 }
 
 /**
@@ -123,20 +187,34 @@
  *   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.
  */
 fun MutableSceneTransitionLayoutState(
     initialScene: SceneKey,
     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 {
     return MutableSceneTransitionLayoutStateImpl(
         initialScene,
         transitions,
+        initialOverlays,
         canChangeScene,
+        canShowOverlay,
+        canHideOverlay,
+        canReplaceOverlay,
         stateLinks,
         enableInterruptions,
     )
@@ -146,7 +224,13 @@
 internal class MutableSceneTransitionLayoutStateImpl(
     initialScene: SceneKey,
     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.
@@ -159,13 +243,18 @@
      * 1. A list with a single [TransitionState.Idle] element, when we are idle.
      * 2. A list with one or more [TransitionState.Transition], when we are transitioning.
      */
-    @VisibleForTesting
     internal var transitionStates: List<TransitionState> by
-        mutableStateOf(listOf(TransitionState.Idle(initialScene)))
+        mutableStateOf(listOf(TransitionState.Idle(initialScene, initialOverlays)))
         private set
 
+    override val currentScene: SceneKey
+        get() = transitionState.currentScene
+
+    override val currentOverlays: Set<OverlayKey>
+        get() = transitionState.currentOverlays
+
     override val transitionState: TransitionState
-        get() = transitionStates.last()
+        get() = transitionStates[transitionStates.lastIndex]
 
     override val currentTransitions: List<TransitionState.Transition>
         get() {
@@ -195,24 +284,29 @@
         }
     }
 
-    override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean {
+    override fun isTransitioning(from: ContentKey?, to: ContentKey?): Boolean {
         val transition = currentTransition ?: return false
         return transition.isTransitioning(from, to)
     }
 
-    override fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
+    override fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
         val transition = currentTransition ?: return false
-        return transition.isTransitioningBetween(scene, other)
+        return transition.isTransitioningBetween(content, other)
+    }
+
+    override fun isTransitioningFromOrTo(content: ContentKey): Boolean {
+        val transition = currentTransition ?: return false
+        return transition.isTransitioningFromOrTo(content)
     }
 
     override fun setTargetScene(
         targetScene: SceneKey,
-        coroutineScope: CoroutineScope,
+        animationScope: CoroutineScope,
         transitionKey: TransitionKey?,
-    ): TransitionState.Transition? {
+    ): Pair<TransitionState.Transition.ChangeScene, Job>? {
         checkThread()
 
-        return coroutineScope.animateToScene(
+        return animationScope.animateToScene(
             layoutState = this@MutableSceneTransitionLayoutStateImpl,
             target = targetScene,
             transitionKey = transitionKey,
@@ -220,44 +314,95 @@
     }
 
     /**
+     * Instantly start a [transition], running it in [animationScope].
+     *
+     * This call returns immediately and [transition] will be the [currentTransition] of this
+     * [MutableSceneTransitionLayoutState].
+     *
+     * @see startTransition
+     */
+    internal fun startTransitionImmediately(
+        animationScope: CoroutineScope,
+        transition: TransitionState.Transition,
+        chain: Boolean = true,
+    ): Job {
+        // Note that we start with UNDISPATCHED so that startTransition() is called directly and
+        // transition becomes the current [transitionState] right after this call.
+        return animationScope.launch(
+            start = CoroutineStart.UNDISPATCHED,
+        ) {
+            startTransition(transition, chain)
+        }
+    }
+
+    /**
      * Start a new [transition].
      *
      * If [chain] is `true`, then the transitions will simply be added to [currentTransitions] and
      * will run in parallel to the current transitions. If [chain] is `false`, then the list of
      * [currentTransitions] will be cleared and [transition] will be the only running transition.
      *
-     * Important: you *must* call [finishTransition] once the transition is finished.
+     * If any transition is currently ongoing, it will be interrupted and forced to animate to its
+     * current state.
+     *
+     * This method returns when [transition] is done running, i.e. when the call to
+     * [run][TransitionState.Transition.run] returns.
      */
-    internal fun startTransition(transition: TransitionState.Transition, chain: Boolean = true) {
+    internal suspend fun startTransition(
+        transition: TransitionState.Transition,
+        chain: Boolean = true,
+    ) {
         checkThread()
 
+        try {
+            // Keep a reference to the previous transition (if any).
+            val previousTransition = currentTransition
+
+            // Start the transition.
+            startTransitionInternal(transition, chain)
+
+            // Handle transition links.
+            previousTransition?.let { cancelActiveTransitionLinks(it) }
+            if (stateLinks.isNotEmpty()) {
+                coroutineScope { setupTransitionLinks(transition) }
+            }
+
+            // Run the transition until it is finished.
+            transition.run()
+        } finally {
+            finishTransition(transition)
+        }
+    }
+
+    private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) {
+        // Set the current scene and overlays on the transition.
+        val currentState = transitionState
+        transition.currentSceneWhenTransitionStarted = currentState.currentScene
+        transition.currentOverlaysWhenTransitionStarted = currentState.currentOverlays
+
         // Compute the [TransformationSpec] when the transition starts.
-        val fromScene = transition.fromScene
-        val toScene = transition.toScene
-        val orientation = (transition as? ContentState.HasOverscrollProperties)?.orientation
+        val fromContent = transition.fromContent
+        val toContent = transition.toContent
+        val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
 
         // Update the transition specs.
         transition.transformationSpec =
             transitions
-                .transitionSpec(fromScene, toScene, key = transition.key)
+                .transitionSpec(fromContent, toContent, key = transition.key)
                 .transformationSpec()
         transition.previewTransformationSpec =
             transitions
-                .transitionSpec(fromScene, toScene, key = transition.key)
+                .transitionSpec(fromContent, toContent, key = transition.key)
                 .previewTransformationSpec()
         if (orientation != null) {
             transition.updateOverscrollSpecs(
-                fromSpec = transitions.overscrollSpec(fromScene, orientation),
-                toSpec = transitions.overscrollSpec(toScene, orientation),
+                fromSpec = transitions.overscrollSpec(fromContent, orientation),
+                toSpec = transitions.overscrollSpec(toContent, orientation),
             )
         } else {
             transition.updateOverscrollSpecs(fromSpec = null, toSpec = null)
         }
 
-        // Handle transition links.
-        currentTransition?.let { cancelActiveTransitionLinks(it) }
-        setupTransitionLinks(transition)
-
         if (!enableInterruptions) {
             // Set the current transition.
             check(transitionStates.size == 1)
@@ -272,9 +417,8 @@
                 transitionStates = listOf(transition)
             }
             is TransitionState.Transition -> {
-                // Force the current transition to finish to currentScene. The transition will call
-                // [finishTransition] once it's finished.
-                currentState.finish()
+                // Force the current transition to finish to currentScene.
+                currentState.freezeAndAnimateToCurrentState()
 
                 val tooManyTransitions = transitionStates.size >= MAX_CONCURRENT_TRANSITIONS
                 val clearCurrentTransitions = !chain || tooManyTransitions
@@ -312,8 +456,8 @@
                 appendLine("  Transitions (size=${transitionStates.size}):")
                 transitionStates.fastForEach { state ->
                     val transition = state as TransitionState.Transition
-                    val from = transition.fromScene
-                    val to = transition.toScene
+                    val from = transition.fromContent
+                    val to = transition.toContent
                     val indicator = if (finishedTransitions.contains(transition)) "x" else " "
                     appendLine("  [$indicator] $from => $to ($transition)")
                 }
@@ -328,7 +472,7 @@
         transition.activeTransitionLinks.clear()
     }
 
-    private fun setupTransitionLinks(transition: TransitionState.Transition) {
+    private fun CoroutineScope.setupTransitionLinks(transition: TransitionState.Transition) {
         stateLinks.fastForEach { stateLink ->
             val matchingLinks =
                 stateLink.transitionLinks.fastFilter { it.isMatchingLink(transition) }
@@ -348,7 +492,11 @@
                     key = matchingLink.targetTransitionKey,
                 )
 
-            stateLink.target.startTransition(linkedTransition)
+            // Start with UNDISPATCHED so that startTransition is called directly and the new linked
+            // transition is observable directly.
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                stateLink.target.startTransition(linkedTransition)
+            }
             transition.activeTransitionLinks[stateLink] = linkedTransition
         }
     }
@@ -358,7 +506,7 @@
      * [currentScene][TransitionState.currentScene]. This will do nothing if [transition] was
      * interrupted since it was started.
      */
-    internal fun finishTransition(transition: TransitionState.Transition) {
+    private fun finishTransition(transition: TransitionState.Transition) {
         checkThread()
 
         if (finishedTransitions.contains(transition)) {
@@ -366,6 +514,10 @@
             return
         }
 
+        // Make sure that this transition settles in case it was force finished, for instance by
+        // calling snapToScene().
+        transition.freezeAndAnimateToCurrentState()
+
         val transitionStates = this.transitionStates
         if (!transitionStates.contains(transition)) {
             // This transition was already removed from transitionStates.
@@ -402,23 +554,28 @@
         // If all transitions are finished, we are idle.
         if (i == nStates) {
             check(finishedTransitions.isEmpty())
-            this.transitionStates = listOf(TransitionState.Idle(lastTransition.currentScene))
+            this.transitionStates =
+                listOf(
+                    TransitionState.Idle(
+                        lastTransition.currentScene,
+                        lastTransition.currentOverlays,
+                    )
+                )
         } else if (i > 0) {
             this.transitionStates = transitionStates.subList(fromIndex = i, toIndex = nStates)
         }
     }
 
-    override fun snapToScene(scene: SceneKey) {
+    override fun snapToScene(scene: SceneKey, currentOverlays: Set<OverlayKey>) {
         checkThread()
 
         // Force finish all transitions.
         while (currentTransitions.isNotEmpty()) {
-            val transition = transitionStates[0] as TransitionState.Transition
-            finishTransition(transition)
+            finishTransition(transitionStates[0] as TransitionState.Transition)
         }
 
         check(transitionStates.size == 1)
-        transitionStates = listOf(TransitionState.Idle(scene))
+        transitionStates = listOf(TransitionState.Idle(scene, currentOverlays))
     }
 
     private fun finishActiveTransitionLinks(transition: TransitionState.Transition) {
@@ -451,8 +608,8 @@
         }
 
         val shouldSnap =
-            (isProgressCloseTo(0f) && transition.currentScene == transition.fromScene) ||
-                (isProgressCloseTo(1f) && transition.currentScene == transition.toScene)
+            (isProgressCloseTo(0f) && transition.currentScene == transition.fromContent) ||
+                (isProgressCloseTo(1f) && transition.currentScene == transition.toContent)
         return if (shouldSnap) {
             finishAllTransitions()
             true
@@ -460,6 +617,137 @@
             false
         }
     }
+
+    override fun showOverlay(
+        overlay: OverlayKey,
+        animationScope: CoroutineScope,
+        transitionKey: TransitionKey?
+    ) {
+        checkThread()
+
+        // Overlay is already shown, do nothing.
+        val currentState = transitionState
+        if (overlay in currentState.currentOverlays) {
+            return
+        }
+
+        val fromScene = currentState.currentScene
+        fun animate(
+            replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+            reversed: Boolean = false,
+        ) {
+            animationScope.showOrHideOverlay(
+                layoutState = this@MutableSceneTransitionLayoutStateImpl,
+                overlay = overlay,
+                fromOrToScene = fromScene,
+                isShowing = true,
+                transitionKey = transitionKey,
+                replacedTransition = replacedTransition,
+                reversed = reversed,
+            )
+        }
+
+        if (
+            currentState is TransitionState.Transition.ShowOrHideOverlay &&
+                currentState.overlay == overlay &&
+                currentState.fromOrToScene == fromScene
+        ) {
+            animate(
+                replacedTransition = currentState,
+                reversed = overlay == currentState.fromContent
+            )
+        } else {
+            animate()
+        }
+    }
+
+    override fun hideOverlay(
+        overlay: OverlayKey,
+        animationScope: CoroutineScope,
+        transitionKey: TransitionKey?
+    ) {
+        checkThread()
+
+        // Overlay is not shown, do nothing.
+        val currentState = transitionState
+        if (!currentState.currentOverlays.contains(overlay)) {
+            return
+        }
+
+        val toScene = currentState.currentScene
+        fun animate(
+            replacedTransition: TransitionState.Transition.ShowOrHideOverlay? = null,
+            reversed: Boolean = false,
+        ) {
+            animationScope.showOrHideOverlay(
+                layoutState = this@MutableSceneTransitionLayoutStateImpl,
+                overlay = overlay,
+                fromOrToScene = toScene,
+                isShowing = false,
+                transitionKey = transitionKey,
+                replacedTransition = replacedTransition,
+                reversed = reversed,
+            )
+        }
+
+        if (
+            currentState is TransitionState.Transition.ShowOrHideOverlay &&
+                currentState.overlay == overlay &&
+                currentState.fromOrToScene == toScene
+        ) {
+            animate(replacedTransition = currentState, reversed = overlay == currentState.toContent)
+        } else {
+            animate()
+        }
+    }
+
+    override fun replaceOverlay(
+        from: OverlayKey,
+        to: OverlayKey,
+        animationScope: CoroutineScope,
+        transitionKey: TransitionKey?
+    ) {
+        checkThread()
+
+        val currentState = transitionState
+        require(from != to) {
+            "replaceOverlay must be called with different overlays (from = to = ${from.debugName})"
+        }
+        require(from in currentState.currentOverlays) {
+            "Overlay ${from.debugName} is not shown so it can't be replaced by ${to.debugName}"
+        }
+        require(to !in currentState.currentOverlays) {
+            "Overlay ${to.debugName} is already shown so it can't replace ${from.debugName}"
+        }
+
+        fun animate(
+            replacedTransition: TransitionState.Transition.ReplaceOverlay? = null,
+            reversed: Boolean = false,
+        ) {
+            animationScope.replaceOverlay(
+                layoutState = this@MutableSceneTransitionLayoutStateImpl,
+                fromOverlay = if (reversed) to else from,
+                toOverlay = if (reversed) from else to,
+                transitionKey = transitionKey,
+                replacedTransition = replacedTransition,
+                reversed = reversed,
+            )
+        }
+
+        if (currentState is TransitionState.Transition.ReplaceOverlay) {
+            if (currentState.fromOverlay == from && currentState.toOverlay == to) {
+                animate(replacedTransition = currentState, reversed = false)
+                return
+            }
+
+            if (currentState.fromOverlay == to && currentState.toOverlay == from) {
+                animate(replacedTransition = currentState, reversed = true)
+                return
+            }
+        }
+
+        animate()
+    }
 }
 
 private const val TAG = "SceneTransitionLayoutState"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d35d956..e65ed9b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -49,16 +49,16 @@
 ) {
     private val transitionCache =
         mutableMapOf<
-            SceneKey,
-            MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
+            ContentKey,
+            MutableMap<ContentKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
         >()
 
     private val overscrollCache =
-        mutableMapOf<SceneKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
+        mutableMapOf<ContentKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
 
     internal fun transitionSpec(
-        from: SceneKey,
-        to: SceneKey,
+        from: ContentKey,
+        to: ContentKey,
         key: TransitionKey?,
     ): TransitionSpecImpl {
         return transitionCache
@@ -67,7 +67,11 @@
             .getOrPut(key) { findSpec(from, to, key) }
     }
 
-    private fun findSpec(from: SceneKey, to: SceneKey, key: TransitionKey?): TransitionSpecImpl {
+    private fun findSpec(
+        from: ContentKey,
+        to: ContentKey,
+        key: TransitionKey?
+    ): TransitionSpecImpl {
         val spec = transition(from, to, key) { it.from == from && it.to == to }
         if (spec != null) {
             return spec
@@ -86,15 +90,24 @@
             return relaxedSpec
         }
 
-        return transition(from, to, key) {
+        val relaxedReversed =
+            transition(from, to, key) {
                 (it.from == to && it.to == null) || (it.to == from && it.from == null)
             }
-            ?.reversed() ?: defaultTransition(from, to)
+        if (relaxedReversed != null) {
+            return relaxedReversed.reversed()
+        }
+
+        return if (key != null) {
+            findSpec(from, to, null)
+        } else {
+            defaultTransition(from, to)
+        }
     }
 
     private fun transition(
-        from: SceneKey,
-        to: SceneKey,
+        from: ContentKey,
+        to: ContentKey,
         key: TransitionKey?,
         filter: (TransitionSpecImpl) -> Boolean,
     ): TransitionSpecImpl? {
@@ -110,16 +123,16 @@
         return match
     }
 
-    private fun defaultTransition(from: SceneKey, to: SceneKey) =
+    private fun defaultTransition(from: ContentKey, to: ContentKey) =
         TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider)
 
-    internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? =
+    internal fun overscrollSpec(scene: ContentKey, orientation: Orientation): OverscrollSpecImpl? =
         overscrollCache
             .getOrPut(scene) { mutableMapOf() }
-            .getOrPut(orientation) { overscroll(scene, orientation) { it.scene == scene } }
+            .getOrPut(orientation) { overscroll(scene, orientation) { it.content == scene } }
 
     private fun overscroll(
-        scene: SceneKey,
+        scene: ContentKey,
         orientation: Orientation,
         filter: (OverscrollSpecImpl) -> Boolean,
     ): OverscrollSpecImpl? {
@@ -264,10 +277,10 @@
         previewTransformationSpec?.invoke()
 }
 
-/** The definition of the overscroll behavior of the [scene]. */
+/** The definition of the overscroll behavior of the [content]. */
 interface OverscrollSpec {
     /** The scene we are over scrolling. */
-    val scene: SceneKey
+    val content: ContentKey
 
     /** The orientation of this [OverscrollSpec]. */
     val orientation: Orientation
@@ -288,7 +301,7 @@
 }
 
 internal class OverscrollSpecImpl(
-    override val scene: SceneKey,
+    override val content: ContentKey,
     override val orientation: Orientation,
     override val transformationSpec: TransformationSpecImpl,
     override val progressConverter: ProgressConverter?,
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..2a09a77
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -0,0 +1,647 @@
+/*
+ * 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.AnimationSpec
+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.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.CompletableDeferred
+
+internal fun createSwipeAnimation(
+    layoutState: MutableSceneTransitionLayoutStateImpl,
+    result: UserActionResult,
+    isUpOrLeft: Boolean,
+    orientation: Orientation,
+    distance: Float,
+): SwipeAnimation<*> {
+    return createSwipeAnimation(
+        layoutState,
+        result,
+        isUpOrLeft,
+        orientation,
+        distance = { distance },
+        contentForUserActions = {
+            error("Computing contentForUserActions requires a SceneTransitionLayoutImpl")
+        },
+    )
+}
+
+internal fun createSwipeAnimation(
+    layoutImpl: SceneTransitionLayoutImpl,
+    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,
+        result,
+        isUpOrLeft,
+        orientation,
+        distance = ::distance,
+        contentForUserActions = { layoutImpl.contentForUserActions().key },
+    )
+}
+
+private fun createSwipeAnimation(
+    layoutState: MutableSceneTransitionLayoutStateImpl,
+    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,
+            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 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
+
+    private var _currentContent by mutableStateOf(currentContent)
+    var currentContent: T
+        get() = _currentContent
+        set(value) {
+            check(!isAnimatingOffset()) {
+                "currentContent can not be changed once we are animating the offset"
+            }
+            _currentContent = value
+        }
+
+    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
+            val offset =
+                when {
+                    isInPreviewStage -> 0f
+                    animatable != null -> animatable.value
+                    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 ?: return 0f
+            val distance = distance()
+            if (distance == DistanceUnspecified) {
+                return 0f
+            }
+
+            val velocityInDistanceUnit = animatable.velocity
+            return velocityInDistanceUnit / distance.absoluteValue
+        }
+
+    val previewProgress: Float
+        get() {
+            val offset =
+                if (isInPreviewStage) {
+                    offsetAnimation?.value ?: dragOffset
+                } else {
+                    dragOffset
+                }
+            return computeProgress(offset)
+        }
+
+    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: Animatable<Float, AnimationVector1D>? by mutableStateOf(null)
+    private val offsetAnimationRunnable = CompletableDeferred<(suspend () -> Unit)?>()
+
+    val isUserInputOngoing: Boolean
+        get() = offsetAnimation == null
+
+    override val absoluteDistance: Float
+        get() = distance().absoluteValue
+
+    constructor(
+        other: SwipeAnimation<T>
+    ) : this(
+        layoutState = other.layoutState,
+        fromContent = other.fromContent,
+        toContent = other.toContent,
+        orientation = other.orientation,
+        isUpOrLeft = other.isUpOrLeft,
+        requiresFullDistanceSwipe = other.requiresFullDistanceSwipe,
+        distance = other.distance,
+        currentContent = other.currentContent,
+        dragOffset = other.offsetAnimation?.value ?: other.dragOffset,
+    )
+
+    suspend fun run() {
+        // This animation will first be driven by finger, then when the user lift their finger we
+        // start an animation to the target offset (progress = 1f or progress = 0f). We await() for
+        // offsetAnimationRunnable to be completed and then run it.
+        val runAnimation = offsetAnimationRunnable.await() ?: return
+        runAnimation()
+    }
+
+    /**
+     * 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)
+
+    fun isAnimatingOffset(): Boolean = offsetAnimation != null
+
+    fun animateOffset(
+        initialVelocity: Float,
+        targetContent: T,
+        spec: AnimationSpec<Float>? = null,
+    ) {
+        check(!isAnimatingOffset()) { "SwipeAnimation.animateOffset() can only be called once" }
+
+        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
+        }
+
+        val startProgress =
+            if (contentTransition.previewTransformationSpec != null && targetContent == toContent) {
+                0f
+            } else {
+                dragOffset
+            }
+
+        val animatable =
+            Animatable(startProgress, OffsetVisibilityThreshold).also { offsetAnimation = it }
+
+        check(isAnimatingOffset())
+
+        // Note: we still create the animatable and set it on offsetAnimation even when
+        // skipAnimation is true, just so that isUserInputOngoing and isAnimatingOffset() are
+        // unchanged even despite this small skip-optimization (which is just an implementation
+        // detail).
+        if (skipAnimation) {
+            // Unblock the job.
+            offsetAnimationRunnable.complete(null)
+            return
+        }
+
+        val isTargetGreater = targetOffset > animatable.value
+        val startedWhenOvercrollingTargetContent =
+            if (targetContent == fromContent) initialProgress < 0f else initialProgress > 1f
+
+        val swipeSpec =
+            spec
+                ?: contentTransition.transformationSpec.swipeSpec
+                ?: layoutState.transitions.defaultSwipeSpec
+
+        offsetAnimationRunnable.complete {
+            try {
+                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)) {
+                                throw SnapException()
+                            }
+                        }
+                    }
+                }
+            } catch (_: SnapException) {
+                /* Ignore. */
+            }
+        }
+    }
+
+    /** An exception thrown during the animation to stop it immediately. */
+    private class SnapException : Exception()
+
+    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)
+            }
+        }
+    }
+
+    fun freezeAndAnimateToCurrentState() {
+        if (isAnimatingOffset()) return
+
+        animateOffset(initialVelocity = 0f, targetContent = currentContent)
+    }
+}
+
+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 suspend fun run() {
+        swipeAnimation.run()
+    }
+
+    override fun freezeAndAnimateToCurrentState() {
+        swipeAnimation.freezeAndAnimateToCurrentState()
+    }
+}
+
+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 suspend fun run() {
+        swipeAnimation.run()
+    }
+
+    override fun freezeAndAnimateToCurrentState() {
+        swipeAnimation.freezeAndAnimateToCurrentState()
+    }
+}
+
+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 suspend fun run() {
+        swipeAnimation.run()
+    }
+
+    override fun freezeAndAnimateToCurrentState() {
+        swipeAnimation.freezeAndAnimateToCurrentState()
+    }
+}
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/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index e38c849..1f82e0b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -25,7 +25,7 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 import kotlin.math.tanh
 
 /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
@@ -104,23 +104,23 @@
     ): TransitionSpec
 
     /**
-     * Define the animation to be played when the [scene] is overscrolled in the given
+     * Define the animation to be played when the [content] is overscrolled in the given
      * [orientation].
      *
      * The overscroll animation always starts from a progress of 0f, and reaches 1f when moving the
      * [distance] down/right, -1f when moving in the opposite direction.
      */
     fun overscroll(
-        scene: SceneKey,
+        content: ContentKey,
         orientation: Orientation,
         builder: OverscrollBuilder.() -> Unit,
     ): OverscrollSpec
 
     /**
-     * Prevents overscroll the [scene] in the given [orientation], allowing ancestors to eventually
-     * consume the remaining gesture.
+     * Prevents overscroll the [content] in the given [orientation], allowing ancestors to
+     * eventually consume the remaining gesture.
      */
-    fun overscrollDisabled(scene: SceneKey, orientation: Orientation): OverscrollSpec
+    fun overscrollDisabled(content: ContentKey, orientation: Orientation): OverscrollSpec
 }
 
 interface BaseTransitionBuilder : PropertyTransformationBuilder {
@@ -262,7 +262,7 @@
      */
     fun contentDuringTransition(
         element: ElementKey,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         fromContentZIndex: Float,
         toContentZIndex: Float,
     ): ContentKey
@@ -278,7 +278,7 @@
      */
     fun pickSingleContentIn(
         contents: Set<ContentKey>,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         element: ElementKey,
     ): ContentKey {
         val fromContent = transition.fromContent
@@ -331,7 +331,7 @@
 object HighestZIndexContentPicker : ElementContentPicker {
     override fun contentDuringTransition(
         element: ElementKey,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         fromContentZIndex: Float,
         toContentZIndex: Float
     ): ContentKey {
@@ -352,7 +352,7 @@
 
             override fun contentDuringTransition(
                 element: ElementKey,
-                transition: ContentState.Transition<*>,
+                transition: TransitionState.Transition,
                 fromContentZIndex: Float,
                 toContentZIndex: Float
             ): ContentKey {
@@ -373,7 +373,7 @@
 object LowestZIndexContentPicker : ElementContentPicker {
     override fun contentDuringTransition(
         element: ElementKey,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         fromContentZIndex: Float,
         toContentZIndex: Float
     ): ContentKey {
@@ -394,7 +394,7 @@
 
             override fun contentDuringTransition(
                 element: ElementKey,
-                transition: ContentState.Transition<*>,
+                transition: TransitionState.Transition,
                 fromContentZIndex: Float,
                 toContentZIndex: Float
             ): ContentKey {
@@ -428,7 +428,7 @@
 ) : StaticElementContentPicker {
     override fun contentDuringTransition(
         element: ElementKey,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         fromContentZIndex: Float,
         toContentZIndex: Float,
     ): ContentKey {
@@ -523,8 +523,8 @@
     fun convert(progress: Float): Float
 
     companion object {
-        /** Keeps scrolling linearly */
-        val Default = linear()
+        /** Starts linearly with some resistance and slowly approaches to 0.2f */
+        val Default = tanh(maxProgress = 0.2f, tilt = 3f)
 
         /**
          * The scroll stays linear, with [factor] you can control how much resistance there is.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 523e5bdd7..da4c8d8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -84,30 +84,30 @@
     }
 
     override fun overscroll(
-        scene: SceneKey,
+        content: ContentKey,
         orientation: Orientation,
         builder: OverscrollBuilder.() -> Unit
     ): OverscrollSpec {
         val impl = OverscrollBuilderImpl().apply(builder)
         check(impl.transformations.isNotEmpty()) {
             "This method does not allow empty transformations. " +
-                "Use overscrollDisabled($scene, $orientation) instead."
+                "Use overscrollDisabled($content, $orientation) instead."
         }
-        return overscrollSpec(scene, orientation, impl)
+        return overscrollSpec(content, orientation, impl)
     }
 
-    override fun overscrollDisabled(scene: SceneKey, orientation: Orientation): OverscrollSpec {
-        return overscrollSpec(scene, orientation, OverscrollBuilderImpl())
+    override fun overscrollDisabled(content: ContentKey, orientation: Orientation): OverscrollSpec {
+        return overscrollSpec(content, orientation, OverscrollBuilderImpl())
     }
 
     private fun overscrollSpec(
-        scene: SceneKey,
+        content: ContentKey,
         orientation: Orientation,
         impl: OverscrollBuilderImpl,
     ): OverscrollSpec {
         val spec =
             OverscrollSpecImpl(
-                scene = scene,
+                content = content,
                 orientation = orientation,
                 transformationSpec =
                     TransformationSpecImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
index 0f66804..9851b32 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/UserActionDistanceScopeImpl.kt
@@ -35,7 +35,7 @@
     }
 
     override fun SceneKey.targetSize(): IntSize? {
-        return layoutImpl.scenes[this]?.targetSize.takeIf { it != IntSize.Zero }
+        return layoutImpl.sceneOrNull(this)?.targetSize.takeIf { it != IntSize.Zero }
     }
 }
 
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 6f608cb..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) {
@@ -96,6 +76,8 @@
                 .approachLayout(
                     isMeasurementApproachInProgress = { layoutImpl.state.isTransitioning() }
                 ) { measurable, constraints ->
+                    // TODO(b/353679003): Use the ModifierNode API to set this *before* the approach
+                    // pass is started.
                     targetSize = lookaheadSize
                     val placeable = measurable.measure(constraints)
                     layout(placeable.width, placeable.height) { placeable.place(0, 0) }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt
new file mode 100644
index 0000000..ccec9e8
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Overlay.kt
@@ -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.compose.animation.scene.content
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+
+/** An overlay defined in a [SceneTransitionLayout]. */
+@Stable
+internal class Overlay(
+    override val key: OverlayKey,
+    layoutImpl: SceneTransitionLayoutImpl,
+    content: @Composable ContentScope.() -> Unit,
+    actions: Map<UserAction.Resolved, UserActionResult>,
+    zIndex: Float,
+    alignment: Alignment,
+) : Content(key, layoutImpl, content, actions, zIndex) {
+    var alignment by mutableStateOf(alignment)
+
+    override fun toString(): String {
+        return "Overlay(key=$key)"
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt
deleted file mode 100644
index 0bd676b..0000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/ContentState.kt
+++ /dev/null
@@ -1,242 +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.compose.animation.scene.content.state
-
-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.Stable
-import com.android.compose.animation.scene.ContentKey
-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.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.TransformationSpec
-import com.android.compose.animation.scene.TransformationSpecImpl
-import com.android.compose.animation.scene.TransitionKey
-import com.android.compose.animation.scene.transition.link.LinkedTransition
-import com.android.compose.animation.scene.transition.link.StateLink
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-/** The state associated to one or more contents. */
-@Stable
-sealed interface ContentState<out T : ContentKey> {
-    /** The [content] is idle, it does not animate. */
-    sealed class Idle<T : ContentKey>(val content: T) : ContentState<T>
-
-    /** The content is transitioning with another content. */
-    sealed class Transition<out T : ContentKey>(
-        val fromContent: T,
-        val toContent: T,
-        internal val replacedTransition: Transition<T>?,
-    ) : ContentState<T> {
-        /**
-         * The key of this transition. This should usually be null, but it can be specified to use a
-         * specific set of transformations associated to this transition.
-         */
-        open val key: TransitionKey? = null
-
-        /**
-         * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
-         * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
-         * when flinging quickly during a swipe gesture.
-         */
-        abstract val progress: Float
-
-        /** The current velocity of [progress], in progress units. */
-        abstract val progressVelocity: Float
-
-        /** Whether the transition was triggered by user input rather than being programmatic. */
-        abstract val isInitiatedByUserInput: Boolean
-
-        /** Whether user input is currently driving the transition. */
-        abstract val isUserInputOngoing: Boolean
-
-        /**
-         * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can
-         * also be less than `0` or greater than `1` when using transitions with a spring
-         * AnimationSpec or when flinging quickly during a swipe gesture.
-         */
-        internal open val previewProgress: Float = 0f
-
-        /** The current velocity of [previewProgress], in progress units. */
-        internal open val previewProgressVelocity: Float = 0f
-
-        /** Whether the transition is currently in the preview stage */
-        internal open val isInPreviewStage: Boolean = false
-
-        /**
-         * The current [TransformationSpecImpl] and [OverscrollSpecImpl] associated to this
-         * transition.
-         *
-         * Important: These will be set exactly once, when this transition is
-         * [started][MutableSceneTransitionLayoutStateImpl.startTransition].
-         */
-        internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
-        internal var previewTransformationSpec: TransformationSpecImpl? = null
-        private var fromOverscrollSpec: OverscrollSpecImpl? = null
-        private var toOverscrollSpec: OverscrollSpecImpl? = null
-
-        /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */
-        internal val currentOverscrollSpec: OverscrollSpecImpl?
-            get() {
-                if (this !is HasOverscrollProperties) return null
-                val progress = progress
-                val bouncingContent = bouncingContent
-                return when {
-                    progress < 0f || bouncingContent == fromContent -> fromOverscrollSpec
-                    progress > 1f || bouncingContent == toContent -> toOverscrollSpec
-                    else -> null
-                }
-            }
-
-        /**
-         * An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden
-         * jump of values when this transitions interrupts another one.
-         */
-        private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null
-
-        /** The map of active links that connects this transition to other transitions. */
-        internal val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
-
-        init {
-            check(fromContent != toContent)
-            check(
-                replacedTransition == null ||
-                    (replacedTransition.fromContent == fromContent &&
-                        replacedTransition.toContent == toContent)
-            )
-        }
-
-        /**
-         * Force this transition to finish and animate to an [Idle] state.
-         *
-         * Important: Once this is called, the effective state of the transition should remain
-         * unchanged. For instance, in the case of a [TransitionState.Transition], its
-         * [currentScene][TransitionState.Transition.currentScene] should never change once [finish]
-         * is called.
-         *
-         * @return the [Job] that animates to the idle state. It can be used to wait until the
-         *   animation is complete or cancel it to snap the animation. Calling [finish] multiple
-         *   times will return the same [Job].
-         */
-        internal abstract fun finish(): Job
-
-        /**
-         * Whether we are transitioning. If [from] or [to] is empty, we will also check that they
-         * match the contents we are animating from and/or to.
-         */
-        fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean {
-            return (from == null || fromContent == from) && (to == null || toContent == to)
-        }
-
-        /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
-        fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
-            return isTransitioning(from = content, to = other) ||
-                isTransitioning(from = other, to = content)
-        }
-
-        internal fun updateOverscrollSpecs(
-            fromSpec: OverscrollSpecImpl?,
-            toSpec: OverscrollSpecImpl?,
-        ) {
-            fromOverscrollSpec = fromSpec
-            toOverscrollSpec = toSpec
-        }
-
-        /** Returns if the [progress] value of this transition can go beyond range `[0; 1]` */
-        internal fun isWithinProgressRange(progress: Float): Boolean {
-            // If the properties are missing we assume that every [Transition] can overscroll
-            if (this !is HasOverscrollProperties) return true
-            // [OverscrollSpec] for the current scene, even if it hasn't started overscrolling yet.
-            val specForCurrentScene =
-                when {
-                    progress <= 0f -> fromOverscrollSpec
-                    progress >= 1f -> toOverscrollSpec
-                    else -> null
-                } ?: return true
-
-            return specForCurrentScene.transformationSpec.transformations.isNotEmpty()
-        }
-
-        internal open fun interruptionProgress(
-            layoutImpl: SceneTransitionLayoutImpl,
-        ): Float {
-            if (!layoutImpl.state.enableInterruptions) {
-                return 0f
-            }
-
-            if (replacedTransition != null) {
-                return replacedTransition.interruptionProgress(layoutImpl)
-            }
-
-            fun create(): Animatable<Float, AnimationVector1D> {
-                val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
-                layoutImpl.coroutineScope.launch {
-                    val swipeSpec = layoutImpl.state.transitions.defaultSwipeSpec
-                    val progressSpec =
-                        spring(
-                            stiffness = swipeSpec.stiffness,
-                            dampingRatio = swipeSpec.dampingRatio,
-                            visibilityThreshold = ProgressVisibilityThreshold,
-                        )
-                    animatable.animateTo(0f, progressSpec)
-                }
-
-                return animatable
-            }
-
-            val animatable = interruptionDecay ?: create().also { interruptionDecay = it }
-            return animatable.value
-        }
-    }
-
-    interface HasOverscrollProperties {
-        /**
-         * The position of the [Transition.toContent].
-         *
-         * Used to understand the direction of the overscroll.
-         */
-        val isUpOrLeft: Boolean
-
-        /**
-         * The relative orientation between [Transition.fromContent] and [Transition.toContent].
-         *
-         * Used to understand the orientation of the overscroll.
-         */
-        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].
-         */
-        val overscrollScope: OverscrollScope
-
-        /**
-         * The content (scene or overlay) around which the transition is currently bouncing. When
-         * not `null`, this transition is currently oscillating around this content and will soon
-         * settle to that content.
-         */
-        val bouncingContent: ContentKey?
-
-        companion object {
-            const val DistanceUnspecified = 0f
-        }
-    }
-}
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 77de22c..a47caaa 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
@@ -16,16 +16,33 @@
 
 package com.android.compose.animation.scene.content.state
 
+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.Stable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+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.OverscrollSpecImpl
+import com.android.compose.animation.scene.ProgressVisibilityThreshold
 import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.TransformationSpec
+import com.android.compose.animation.scene.TransformationSpecImpl
+import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.transition.link.LinkedTransition
+import com.android.compose.animation.scene.transition.link.StateLink
+import kotlinx.coroutines.launch
 
-/** The state associated to one or more scenes. */
-// TODO(b/353679003): Rename to SceneState.
+/** The state associated to a [SceneTransitionLayout] at some specific point in time. */
 @Stable
-sealed interface TransitionState : ContentState<SceneKey> {
+sealed interface TransitionState {
     /**
-     * The current effective scene. If a new transition was triggered, it would start from this
-     * scene.
+     * The current effective scene. If a new scene transition was triggered, it would start from
+     * this scene.
      *
      * For instance, when swiping from scene A to scene B, the [currentScene] is A when the swipe
      * gesture starts, but then if the user flings their finger and commits the transition to scene
@@ -34,20 +51,353 @@
      */
     val currentScene: SceneKey
 
+    /**
+     * The current set of overlays. This represents the set of overlays that will be visible on
+     * screen once all transitions are finished.
+     *
+     * @see MutableSceneTransitionLayoutState.showOverlay
+     * @see MutableSceneTransitionLayoutState.hideOverlay
+     * @see MutableSceneTransitionLayoutState.replaceOverlay
+     */
+    val currentOverlays: Set<OverlayKey>
+
     /** The scene [currentScene] is idle. */
     data class Idle(
         override val currentScene: SceneKey,
-    ) : TransitionState, ContentState.Idle<SceneKey>(currentScene)
+        override val currentOverlays: Set<OverlayKey> = emptySet(),
+    ) : TransitionState
 
-    /** There is a transition animating between [fromScene] and [toScene]. */
-    abstract class Transition(
-        /** The scene this transition is starting from. Can't be the same as toScene */
-        val fromScene: SceneKey,
+    sealed class Transition(
+        val fromContent: ContentKey,
+        val toContent: ContentKey,
+        val replacedTransition: Transition? = null,
+    ) : TransitionState {
+        /** A transition animating between [fromScene] and [toScene]. */
+        abstract class ChangeScene(
+            /** The scene this transition is starting from. Can't be the same as toScene */
+            val fromScene: SceneKey,
 
-        /** The scene this transition is going to. Can't be the same as fromScene */
-        val toScene: SceneKey,
+            /** The scene this transition is going to. Can't be the same as fromScene */
+            val toScene: SceneKey,
 
-        /** The transition that `this` transition is replacing, if any. */
-        replacedTransition: Transition? = null,
-    ) : TransitionState, ContentState.Transition<SceneKey>(fromScene, toScene, replacedTransition)
+            /** The transition that `this` transition is replacing, if any. */
+            replacedTransition: Transition? = null,
+        ) : Transition(fromScene, toScene, replacedTransition) {
+            final override val currentOverlays: Set<OverlayKey>
+                get() {
+                    // The set of overlays does not change in a [ChangeCurrentScene] transition.
+                    return currentOverlaysWhenTransitionStarted
+                }
+        }
+
+        /**
+         * A transition that is animating one or more overlays and for which [currentOverlays] will
+         * change over the course of the transition.
+         */
+        sealed class OverlayTransition(
+            fromContent: ContentKey,
+            toContent: ContentKey,
+            replacedTransition: Transition?,
+        ) : Transition(fromContent, toContent, replacedTransition) {
+            final override val currentScene: SceneKey
+                get() {
+                    // The current scene does not change during overlay transitions.
+                    return currentSceneWhenTransitionStarted
+                }
+
+            // Note: We use deriveStateOf() so that the computed set is cached and reused when the
+            // inputs of the computations don't change, to avoid recomputing and allocating a new
+            // set every time currentOverlays is called (which is every frame and for each element).
+            final override val currentOverlays: Set<OverlayKey> by derivedStateOf {
+                computeCurrentOverlays()
+            }
+
+            protected abstract fun computeCurrentOverlays(): Set<OverlayKey>
+        }
+
+        /** The [overlay] is either showing from [fromOrToScene] or hiding into [fromOrToScene]. */
+        abstract class ShowOrHideOverlay(
+            val overlay: OverlayKey,
+            val fromOrToScene: SceneKey,
+            fromContent: ContentKey,
+            toContent: ContentKey,
+            replacedTransition: Transition? = null,
+        ) : OverlayTransition(fromContent, toContent, replacedTransition) {
+            /**
+             * Whether [overlay] is effectively shown. For instance, this will be `false` when
+             * starting a swipe transition to show [overlay] and will be `true` only once the swipe
+             * transition is committed.
+             */
+            protected abstract val isEffectivelyShown: Boolean
+
+            init {
+                check(
+                    (fromContent == fromOrToScene && toContent == overlay) ||
+                        (fromContent == overlay && toContent == fromOrToScene)
+                )
+            }
+
+            final override fun computeCurrentOverlays(): Set<OverlayKey> {
+                return if (isEffectivelyShown) {
+                    currentOverlaysWhenTransitionStarted + overlay
+                } else {
+                    currentOverlaysWhenTransitionStarted - overlay
+                }
+            }
+        }
+
+        /** We are transitioning from [fromOverlay] to [toOverlay]. */
+        abstract class ReplaceOverlay(
+            val fromOverlay: OverlayKey,
+            val toOverlay: OverlayKey,
+            replacedTransition: Transition? = null,
+        ) :
+            OverlayTransition(
+                fromContent = fromOverlay,
+                toContent = toOverlay,
+                replacedTransition,
+            ) {
+            /**
+             * The current effective overlay, either [fromOverlay] or [toOverlay]. For instance,
+             * this will be [fromOverlay] when starting a swipe transition that replaces
+             * [fromOverlay] by [toOverlay] and will [toOverlay] once the swipe transition is
+             * committed.
+             */
+            protected abstract val effectivelyShownOverlay: OverlayKey
+
+            init {
+                check(fromOverlay != toOverlay)
+            }
+
+            final override fun computeCurrentOverlays(): Set<OverlayKey> {
+                return when (effectivelyShownOverlay) {
+                    fromOverlay ->
+                        computeCurrentOverlays(include = fromOverlay, exclude = toOverlay)
+                    toOverlay -> computeCurrentOverlays(include = toOverlay, exclude = fromOverlay)
+                    else ->
+                        error(
+                            "effectivelyShownOverlay=$effectivelyShownOverlay, should be " +
+                                "equal to fromOverlay=$fromOverlay or toOverlay=$toOverlay"
+                        )
+                }
+            }
+
+            private fun computeCurrentOverlays(
+                include: OverlayKey,
+                exclude: OverlayKey
+            ): Set<OverlayKey> {
+                return buildSet {
+                    addAll(currentOverlaysWhenTransitionStarted)
+                    remove(exclude)
+                    add(include)
+                }
+            }
+        }
+
+        /**
+         * The current scene and overlays observed right when this transition started. These are set
+         * when this transition is started in
+         * [com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl.startTransition].
+         */
+        internal lateinit var currentSceneWhenTransitionStarted: SceneKey
+        internal lateinit var currentOverlaysWhenTransitionStarted: Set<OverlayKey>
+
+        /**
+         * The key of this transition. This should usually be null, but it can be specified to use a
+         * specific set of transformations associated to this transition.
+         */
+        open val key: TransitionKey? = null
+
+        /**
+         * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
+         * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
+         * when flinging quickly during a swipe gesture.
+         */
+        abstract val progress: Float
+
+        /** The current velocity of [progress], in progress units. */
+        abstract val progressVelocity: Float
+
+        /** Whether the transition was triggered by user input rather than being programmatic. */
+        abstract val isInitiatedByUserInput: Boolean
+
+        /** Whether user input is currently driving the transition. */
+        abstract val isUserInputOngoing: Boolean
+
+        /**
+         * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can
+         * also be less than `0` or greater than `1` when using transitions with a spring
+         * AnimationSpec or when flinging quickly during a swipe gesture.
+         */
+        internal open val previewProgress: Float = 0f
+
+        /** The current velocity of [previewProgress], in progress units. */
+        internal open val previewProgressVelocity: Float = 0f
+
+        /** Whether the transition is currently in the preview stage */
+        internal open val isInPreviewStage: Boolean = false
+
+        /**
+         * The current [TransformationSpecImpl] and [OverscrollSpecImpl] associated to this
+         * transition.
+         *
+         * Important: These will be set exactly once, when this transition is
+         * [started][MutableSceneTransitionLayoutStateImpl.startTransition].
+         */
+        internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+        internal var previewTransformationSpec: TransformationSpecImpl? = null
+        private var fromOverscrollSpec: OverscrollSpecImpl? = null
+        private var toOverscrollSpec: OverscrollSpecImpl? = null
+
+        /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */
+        internal val currentOverscrollSpec: OverscrollSpecImpl?
+            get() {
+                if (this !is HasOverscrollProperties) return null
+                val progress = progress
+                val bouncingContent = bouncingContent
+                return when {
+                    progress < 0f || bouncingContent == fromContent -> fromOverscrollSpec
+                    progress > 1f || bouncingContent == toContent -> toOverscrollSpec
+                    else -> null
+                }
+            }
+
+        /**
+         * An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden
+         * jump of values when this transitions interrupts another one.
+         */
+        private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null
+
+        /** The map of active links that connects this transition to other transitions. */
+        internal val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+
+        init {
+            check(fromContent != toContent)
+            check(
+                replacedTransition == null ||
+                    (replacedTransition.fromContent == fromContent &&
+                        replacedTransition.toContent == toContent)
+            )
+        }
+
+        /**
+         * Whether we are transitioning. If [from] or [to] is empty, we will also check that they
+         * match the contents we are animating from and/or to.
+         */
+        fun isTransitioning(from: ContentKey? = null, to: ContentKey? = null): Boolean {
+            return (from == null || fromContent == from) && (to == null || toContent == to)
+        }
+
+        /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
+        fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
+            return isTransitioning(from = content, to = other) ||
+                isTransitioning(from = other, to = content)
+        }
+
+        /** Whether we are transitioning from or to [content]. */
+        fun isTransitioningFromOrTo(content: ContentKey): Boolean {
+            return fromContent == content || toContent == content
+        }
+
+        /** Run this transition and return once it is finished. */
+        internal abstract suspend fun run()
+
+        /**
+         * Freeze this transition state so that neither [currentScene] nor [currentOverlays] will
+         * change in the future, and animate the progress towards that state. For instance, a
+         * [Transition.ChangeScene] should animate the progress to 0f if its [currentScene] is equal
+         * to its [fromScene][Transition.ChangeScene.fromScene] or animate it to 1f if its equal to
+         * its [toScene][Transition.ChangeScene.toScene].
+         *
+         * This is called when this transition is interrupted (replaced) by another transition.
+         */
+        internal abstract fun freezeAndAnimateToCurrentState()
+
+        internal fun updateOverscrollSpecs(
+            fromSpec: OverscrollSpecImpl?,
+            toSpec: OverscrollSpecImpl?,
+        ) {
+            fromOverscrollSpec = fromSpec
+            toOverscrollSpec = toSpec
+        }
+
+        /** Returns if the [progress] value of this transition can go beyond range `[0; 1]` */
+        internal fun isWithinProgressRange(progress: Float): Boolean {
+            // If the properties are missing we assume that every [Transition] can overscroll
+            if (this !is HasOverscrollProperties) return true
+            // [OverscrollSpec] for the current scene, even if it hasn't started overscrolling yet.
+            val specForCurrentScene =
+                when {
+                    progress <= 0f -> fromOverscrollSpec
+                    progress >= 1f -> toOverscrollSpec
+                    else -> null
+                } ?: return true
+
+            return specForCurrentScene.transformationSpec.transformations.isNotEmpty()
+        }
+
+        internal open fun interruptionProgress(
+            layoutImpl: SceneTransitionLayoutImpl,
+        ): Float {
+            if (!layoutImpl.state.enableInterruptions) {
+                return 0f
+            }
+
+            if (replacedTransition != null) {
+                return replacedTransition.interruptionProgress(layoutImpl)
+            }
+
+            fun create(): Animatable<Float, AnimationVector1D> {
+                val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
+                layoutImpl.animationScope.launch {
+                    val swipeSpec = layoutImpl.state.transitions.defaultSwipeSpec
+                    val progressSpec =
+                        spring(
+                            stiffness = swipeSpec.stiffness,
+                            dampingRatio = swipeSpec.dampingRatio,
+                            visibilityThreshold = ProgressVisibilityThreshold,
+                        )
+                    animatable.animateTo(0f, progressSpec)
+                }
+
+                return animatable
+            }
+
+            val animatable = interruptionDecay ?: create().also { interruptionDecay = it }
+            return animatable.value
+        }
+    }
+
+    interface HasOverscrollProperties {
+        /**
+         * The position of the [Transition.toContent].
+         *
+         * Used to understand the direction of the overscroll.
+         */
+        val isUpOrLeft: Boolean
+
+        /**
+         * The relative orientation between [Transition.fromContent] and [Transition.toContent].
+         *
+         * Used to understand the orientation of the overscroll.
+         */
+        val orientation: Orientation
+
+        /**
+         * Return the absolute distance between fromScene and toScene, if available, otherwise
+         * [DistanceUnspecified].
+         */
+        val absoluteDistance: Float
+
+        /**
+         * The content (scene or overlay) around which the transition is currently bouncing. When
+         * not `null`, this transition is currently oscillating around this content and will soon
+         * settle to that content.
+         */
+        val bouncingContent: ContentKey?
+
+        companion object {
+            const val DistanceUnspecified = 0f
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 538ce79..c5a3067c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -22,7 +22,7 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 
 /** Anchor the size of an element to the size of another element. */
 internal class AnchoredSize(
@@ -36,7 +36,7 @@
         content: ContentKey,
         element: Element,
         stateInContent: Element.State,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         value: IntSize,
     ): IntSize {
         fun anchorSizeIn(content: ContentKey): IntSize {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index 258f541..05878c2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -23,7 +23,7 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 
 /** Anchor the translation of an element to another element. */
 internal class AnchoredTranslate(
@@ -35,7 +35,7 @@
         content: ContentKey,
         element: Element,
         stateInContent: Element.State,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         value: Offset,
     ): Offset {
         fun throwException(content: ContentKey?): Nothing {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
index be8dac21..7f86479 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -22,7 +22,7 @@
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.Scale
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 
 /**
  * Scales the draw size of an element. Note this will only scale the draw inside of an element,
@@ -40,7 +40,7 @@
         content: ContentKey,
         element: Element,
         stateInContent: Element.State,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         value: Scale,
     ): Scale {
         return Scale(scaleX, scaleY, pivot)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index d72e43a..a32c7dd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -22,7 +22,7 @@
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 
 /** Translate an element from an edge of the layout. */
 internal class EdgeTranslate(
@@ -35,7 +35,7 @@
         content: ContentKey,
         element: Element,
         stateInContent: Element.State,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         value: Offset
     ): Offset {
         val sceneSize = layoutImpl.content(content).targetSize
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index 92ae30f8..4528eef 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -20,7 +20,7 @@
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 
 /** Fade an element in or out. */
 internal class Fade(
@@ -31,7 +31,7 @@
         content: ContentKey,
         element: Element,
         stateInContent: Element.State,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         value: Float
     ): Float {
         // Return the alpha value of [element] either when it starts fading in or when it finished
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index e8515dc..5f3fdaf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -21,7 +21,7 @@
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 import kotlin.math.roundToInt
 
 /**
@@ -38,7 +38,7 @@
         content: ContentKey,
         element: Element,
         stateInContent: Element.State,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         value: IntSize,
     ): IntSize {
         return IntSize(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index eda8ede..505ad04 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -25,7 +25,7 @@
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 
 /** A transformation applied to one or more elements during a transition. */
 sealed interface Transformation {
@@ -66,7 +66,7 @@
         content: ContentKey,
         element: Element,
         stateInContent: Element.State,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         value: T,
     ): T
 }
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 fab4ced..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
@@ -24,7 +25,7 @@
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.OverscrollScope
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.content.state.ContentState
+import com.android.compose.animation.scene.content.state.TransitionState
 
 internal class Translate(
     override val matcher: ElementMatcher,
@@ -36,7 +37,7 @@
         content: ContentKey,
         element: Element,
         stateInContent: Element.State,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         value: Offset,
     ): Offset {
         return with(layoutImpl.density) {
@@ -53,22 +54,61 @@
     val x: OverscrollScope.() -> Float = { 0f },
     val y: OverscrollScope.() -> Float = { 0f },
 ) : PropertyTransformation<Offset> {
+    private val cachedOverscrollScope = CachedOverscrollScope()
+
     override fun transform(
         layoutImpl: SceneTransitionLayoutImpl,
         content: ContentKey,
         element: Element,
         stateInContent: Element.State,
-        transition: ContentState.Transition<*>,
+        transition: TransitionState.Transition,
         value: Offset,
     ): Offset {
         // As this object is created by OverscrollBuilderImpl and we retrieve the current
         // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
         // that this method was invoked after performing this check.
-        val overscrollProperties = transition as ContentState.HasOverscrollProperties
+        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/Seek.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
new file mode 100644
index 0000000..715d979
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
@@ -0,0 +1,187 @@
+/*
+ * 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.transition
+
+import androidx.annotation.FloatRange
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.util.fastCoerceIn
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SwipeAnimation
+import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.UserActionResult
+import com.android.compose.animation.scene.createSwipeAnimation
+import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Seek to the given [scene] using [progress].
+ *
+ * This will start a transition from the
+ * [current scene][MutableSceneTransitionLayoutState.currentScene] to [scene], driven by the
+ * progress in [progress]. Once [progress] stops emitting, we will animate progress to 1f (using
+ * [animationSpec]) if it stopped normally or to 0f if it stopped with a
+ * [kotlin.coroutines.cancellation.CancellationException].
+ */
+suspend fun MutableSceneTransitionLayoutState.seekToScene(
+    scene: SceneKey,
+    @FloatRange(0.0, 1.0) progress: Flow<Float>,
+    transitionKey: TransitionKey? = null,
+    animationSpec: AnimationSpec<Float>? = null,
+) {
+    require(scene != currentScene) {
+        "seekToScene($scene) has to be called with a different scene than the current scene"
+    }
+
+    seek(UserActionResult.ChangeScene(scene, transitionKey), progress, animationSpec)
+}
+
+/**
+ * Seek to show the given [overlay] using [progress].
+ *
+ * This will start a transition to show [overlay] from the
+ * [current scene][MutableSceneTransitionLayoutState.currentScene], driven by the progress in
+ * [progress]. Once [progress] stops emitting, we will animate progress to 1f (using
+ * [animationSpec]) if it stopped normally or to 0f if it stopped with a
+ * [kotlin.coroutines.cancellation.CancellationException].
+ */
+suspend fun MutableSceneTransitionLayoutState.seekToShowOverlay(
+    overlay: OverlayKey,
+    @FloatRange(0.0, 1.0) progress: Flow<Float>,
+    transitionKey: TransitionKey? = null,
+    animationSpec: AnimationSpec<Float>? = null,
+) {
+    require(overlay in currentOverlays) {
+        "seekToShowOverlay($overlay) can be called only when the overlay is in currentOverlays"
+    }
+
+    seek(UserActionResult.ShowOverlay(overlay, transitionKey), progress, animationSpec)
+}
+
+/**
+ * Seek to hide the given [overlay] using [progress].
+ *
+ * This will start a transition to hide [overlay] to the
+ * [current scene][MutableSceneTransitionLayoutState.currentScene], driven by the progress in
+ * [progress]. Once [progress] stops emitting, we will animate progress to 1f (using
+ * [animationSpec]) if it stopped normally or to 0f if it stopped with a
+ * [kotlin.coroutines.cancellation.CancellationException].
+ */
+suspend fun MutableSceneTransitionLayoutState.seekToHideOverlay(
+    overlay: OverlayKey,
+    @FloatRange(0.0, 1.0) progress: Flow<Float>,
+    transitionKey: TransitionKey? = null,
+    animationSpec: AnimationSpec<Float>? = null,
+) {
+    require(overlay !in currentOverlays) {
+        "seekToHideOverlay($overlay) can be called only when the overlay is *not* in " +
+            "currentOverlays"
+    }
+
+    seek(UserActionResult.HideOverlay(overlay, transitionKey), progress, animationSpec)
+}
+
+private suspend fun MutableSceneTransitionLayoutState.seek(
+    result: UserActionResult,
+    progress: Flow<Float>,
+    animationSpec: AnimationSpec<Float>?,
+) {
+    val layoutState =
+        when (this) {
+            is MutableSceneTransitionLayoutStateImpl -> this
+        }
+
+    val swipeAnimation =
+        createSwipeAnimation(
+            layoutState = layoutState,
+            result = result,
+
+            // We are animating progress, so distance is always 1f.
+            distance = 1f,
+
+            // The orientation and isUpOrLeft don't matter here given that they are only used during
+            // overscroll, which is disabled for progress-based transitions.
+            orientation = Orientation.Horizontal,
+            isUpOrLeft = false,
+        )
+
+    animateProgress(
+        state = layoutState,
+        animation = swipeAnimation,
+        progress = progress,
+        commitSpec = animationSpec,
+        cancelSpec = animationSpec,
+    )
+}
+
+internal suspend fun <T : ContentKey> animateProgress(
+    state: MutableSceneTransitionLayoutStateImpl,
+    animation: SwipeAnimation<T>,
+    progress: Flow<Float>,
+    commitSpec: AnimationSpec<Float>?,
+    cancelSpec: AnimationSpec<Float>?,
+) {
+    fun animateOffset(targetContent: T, spec: AnimationSpec<Float>?) {
+        if (state.transitionState != animation.contentTransition || animation.isAnimatingOffset()) {
+            return
+        }
+
+        animation.animateOffset(
+            initialVelocity = 0f,
+            targetContent = targetContent,
+
+            // Important: we have to specify a spec that correctly animates *progress* (low
+            // visibility threshold) and not *offset* (higher visibility threshold).
+            spec = spec ?: animation.contentTransition.transformationSpec.progressSpec,
+        )
+    }
+
+    coroutineScope {
+        val collectionJob = launch {
+            try {
+                progress.collectLatest { progress ->
+                    // Progress based animation should never overscroll given that the
+                    // absoluteDistance exposed to overscroll builders is always 1f and will not
+                    // lead to any noticeable transformation.
+                    animation.dragOffset = progress.fastCoerceIn(0f, 1f)
+                }
+
+                // Transition committed.
+                animateOffset(animation.toContent, commitSpec)
+            } catch (e: CancellationException) {
+                // Transition cancelled.
+                animateOffset(animation.fromContent, cancelSpec)
+            }
+        }
+
+        // Start the transition.
+        state.startTransition(animation.contentTransition)
+
+        // The transition is done. Cancel the collection in case the transition was finished because
+        // it was interrupted by another transition.
+        if (collectionJob.isActive) {
+            collectionJob.cancel()
+        }
+    }
+}
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 23bcf10..42ba9ba 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
@@ -19,7 +19,6 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.TransitionKey
 import com.android.compose.animation.scene.content.state.TransitionState
-import kotlinx.coroutines.Job
 
 /** A linked transition which is driven by a [originalTransition]. */
 internal class LinkedTransition(
@@ -27,13 +26,13 @@
     fromScene: SceneKey,
     toScene: SceneKey,
     override val key: TransitionKey? = null,
-) : TransitionState.Transition(fromScene, toScene) {
+) : TransitionState.Transition.ChangeScene(fromScene, toScene) {
 
     override val currentScene: SceneKey
         get() {
             return when (originalTransition.currentScene) {
-                originalTransition.fromScene -> fromScene
-                originalTransition.toScene -> toScene
+                originalTransition.fromContent -> fromScene
+                originalTransition.toContent -> toScene
                 else -> error("Original currentScene is neither FromScene nor ToScene")
             }
         }
@@ -50,5 +49,11 @@
     override val progressVelocity: Float
         get() = originalTransition.progressVelocity
 
-    override fun finish(): Job = originalTransition.finish()
+    override suspend fun run() {
+        originalTransition.run()
+    }
+
+    override fun freezeAndAnimateToCurrentState() {
+        originalTransition.freezeAndAnimateToCurrentState()
+    }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
index c0c40dd..c830ca4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.animation.scene.transition.link
 
+import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneTransitionLayoutState
@@ -35,8 +36,8 @@
      * target to `SceneA` from any current scene.
      */
     class TransitionLink(
-        val sourceFrom: SceneKey?,
-        val sourceTo: SceneKey?,
+        val sourceFrom: ContentKey?,
+        val sourceTo: ContentKey?,
         val targetFrom: SceneKey?,
         val targetTo: SceneKey,
         val targetTransitionKey: TransitionKey? = null,
@@ -49,14 +50,16 @@
                 error("From and To can't be the same")
         }
 
-        internal fun isMatchingLink(transition: TransitionState.Transition): Boolean {
-            return (sourceFrom == null || sourceFrom == transition.fromScene) &&
-                (sourceTo == null || sourceTo == transition.toScene)
+        internal fun isMatchingLink(
+            transition: TransitionState.Transition,
+        ): Boolean {
+            return (sourceFrom == null || sourceFrom == transition.fromContent) &&
+                (sourceTo == null || sourceTo == transition.toContent)
         }
 
-        internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
-            return (targetFrom == null || targetFrom == targetCurrentScene) &&
-                targetTo != targetCurrentScene
+        internal fun targetIsInValidState(targetCurrentContent: ContentKey): Boolean {
+            return (targetFrom == null || targetFrom == targetCurrentContent) &&
+                targetTo != targetCurrentContent
         }
     }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 01895c9..a491349 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -39,7 +39,10 @@
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.animation.scene.TestScenes.SceneD
+import com.android.compose.test.setContentAndCreateMainScope
+import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Rule
@@ -168,10 +171,7 @@
                 assertThat(lastValueInTo).isEqualTo(expectedValues)
             }
 
-            after {
-                assertThat(lastValueInFrom).isEqualTo(toValues)
-                assertThat(lastValueInTo).isEqualTo(toValues)
-            }
+            after { assertThat(lastValueInTo).isEqualTo(toValues) }
         }
     }
 
@@ -229,10 +229,7 @@
                 assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
             }
 
-            after {
-                assertThat(lastValueInFrom).isEqualTo(toValues)
-                assertThat(lastValueInTo).isEqualTo(toValues)
-            }
+            after { assertThat(lastValueInTo).isEqualTo(toValues) }
         }
     }
 
@@ -288,10 +285,7 @@
                 assertThat(lastValueInTo).isEqualTo(expectedValues)
             }
 
-            after {
-                assertThat(lastValueInFrom).isEqualTo(toValues)
-                assertThat(lastValueInTo).isEqualTo(toValues)
-            }
+            after { assertThat(lastValueInTo).isEqualTo(toValues) }
         }
     }
 
@@ -415,30 +409,33 @@
             }
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state) {
-                // foo goes from 0f to 100f in A => B.
-                scene(SceneA) { animateFloat(0f, foo) }
-                scene(SceneB) { animateFloat(100f, foo) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    // foo goes from 0f to 100f in A => B.
+                    scene(SceneA) { animateFloat(0f, foo) }
+                    scene(SceneB) { animateFloat(100f, foo) }
 
-                // bar goes from 0f to 10f in C => D.
-                scene(SceneC) { animateFloat(0f, bar) }
-                scene(SceneD) { animateFloat(10f, bar) }
+                    // bar goes from 0f to 10f in C => D.
+                    scene(SceneC) { animateFloat(0f, bar) }
+                    scene(SceneD) { animateFloat(10f, bar) }
+                }
             }
-        }
 
-        rule.runOnUiThread {
-            // A => B is at 30%.
+        // A => B is at 30%.
+        scope.launch {
             state.startTransition(
                 transition(
                     from = SceneA,
                     to = SceneB,
                     progress = { 0.3f },
-                    onFinish = neverFinish(),
+                    onFreezeAndAnimate = { /* never finish */ },
                 )
             )
+        }
 
-            // C => D is at 70%.
+        // C => D is at 70%.
+        scope.launch {
             state.startTransition(transition(from = SceneC, to = SceneD, progress = { 0.7f }))
         }
         rule.waitForIdle()
@@ -475,17 +472,18 @@
             }
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state) {
-                scene(SceneA) { animateFloat(0f, key) }
-                scene(SceneB) { animateFloat(100f, key) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    scene(SceneA) { animateFloat(0f, key) }
+                    scene(SceneB) { animateFloat(100f, key) }
+                }
             }
-        }
 
         // Overscroll on A at -100%: value should be interpolated given that there is no overscroll
         // defined for scene A.
         var progress by mutableStateOf(-1f)
-        rule.runOnIdle {
+        scope.launch {
             state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress }))
         }
         rule.waitForIdle()
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 dc5b2f7..79f82c9 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
@@ -28,10 +28,11 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.Velocity
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes
 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
@@ -40,9 +41,9 @@
 import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.MonotonicClockTestScope
 import com.android.compose.test.runMonotonicClockTest
+import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.launch
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,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 =
@@ -66,19 +67,19 @@
         var layoutDirection = LayoutDirection.Rtl
             set(value) {
                 field = value
-                layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+                layoutImpl.updateContents(scenesBuilder, layoutDirection)
             }
 
         var mutableUserActionsA = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
             set(value) {
                 field = value
-                layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+                layoutImpl.updateContents(scenesBuilder, layoutDirection)
             }
 
         var mutableUserActionsB = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
             set(value) {
                 field = value
-                layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+                layoutImpl.updateContents(scenesBuilder, layoutDirection)
             }
 
         private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
@@ -104,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
@@ -116,9 +132,12 @@
                     swipeSourceDetector = DefaultEdgeDetector,
                     transitionInterceptionThreshold = transitionInterceptionThreshold,
                     builder = scenesBuilder,
-                    coroutineScope = testScope,
+
+                    // Use testScope and not backgroundScope here because backgroundScope does not
+                    // work well with advanceUntilIdle(), which is used by some tests.
+                    animationScope = testScope,
                 )
-                .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
+                .apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }
 
         val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
         val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
@@ -181,13 +200,19 @@
             fromScene: SceneKey? = null,
             toScene: SceneKey? = null,
             progress: Float? = null,
+            previewProgress: Float? = null,
+            isInPreviewStage: Boolean? = null,
             isUserInputOngoing: Boolean? = null
         ): Transition {
-            val transition = assertThat(transitionState).isTransition()
+            val transition = assertThat(transitionState).isSceneTransition()
             currentScene?.let { assertThat(transition).hasCurrentScene(it) }
             fromScene?.let { assertThat(transition).hasFromScene(it) }
             toScene?.let { assertThat(transition).hasToScene(it) }
             progress?.let { assertThat(transition).hasProgress(it) }
+            previewProgress?.let { assertThat(transition).hasPreviewProgress(it) }
+            isInPreviewStage?.let {
+                assertThat(transition).run { if (it) isInPreviewStage() else isNotInPreviewStage() }
+            }
             isUserInputOngoing?.let { assertThat(transition).hasIsUserInputOngoing(it) }
             return transition
         }
@@ -285,8 +310,20 @@
         runMonotonicClockTest {
             val testGestureScope = TestGestureScope(testScope = this)
 
-            // run the test
-            testGestureScope.block()
+            try {
+                // Run the test.
+                testGestureScope.block()
+            } finally {
+                // Make sure we stop the last transition if it was not explicitly stopped, otherwise
+                // tests will time out after 10s given that the transitions are now started on the
+                // test scope. We don't use backgroundScope when starting the test transitions
+                // because coroutines started on the background scope don't work well with
+                // advanceUntilIdle(), which is used in a few tests.
+                if (testGestureScope.draggableHandler.isDrivingTransition) {
+                    (testGestureScope.layoutState.transitionState as Transition)
+                        .freezeAndAnimateToCurrentState()
+                }
+            }
         }
     }
 
@@ -322,6 +359,32 @@
     }
 
     @Test
+    fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene_previewAnimated() =
+        runGestureTest {
+            layoutState.transitions = transitions {
+                // set a preview for the transition
+                from(SceneA, to = SceneC, preview = {}) {}
+            }
+            val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+            assertTransition(currentScene = SceneA)
+
+            dragController.onDragStopped(velocity = velocityThreshold - 0.01f)
+            runCurrent()
+
+            // verify that transition remains in preview stage and animates back to fromScene
+            assertTransition(
+                currentScene = SceneA,
+                isInPreviewStage = true,
+                previewProgress = 0.1f,
+                progress = 0f
+            )
+
+            // wait for the stop animation
+            advanceUntilIdle()
+            assertIdle(currentScene = SceneA)
+        }
+
+    @Test
     fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
         val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
         assertTransition(currentScene = SceneA)
@@ -460,13 +523,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(
@@ -501,7 +565,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)
@@ -730,13 +794,6 @@
     }
 
     @Test
-    fun flingAfterScroll_DuringTransitionBetweenScenes_doNothing() = runGestureTest {
-        flingAfterScroll(use = DuringTransitionBetweenScenes, idleAfterScroll = true)
-
-        assertIdle(currentScene = SceneA)
-    }
-
-    @Test
     fun flingAfterScroll_EdgeNoOverscroll_goToNextScene() = runGestureTest {
         flingAfterScroll(use = EdgeNoPreview, idleAfterScroll = false)
 
@@ -789,13 +846,6 @@
     }
 
     @Test
-    fun flingAfterScrollStartedInScene_DuringTransitionBetweenScenes_doNothing() = runGestureTest {
-        flingAfterScrollStartedInScene(use = DuringTransitionBetweenScenes, idleAfterScroll = true)
-
-        assertIdle(currentScene = SceneA)
-    }
-
-    @Test
     fun flingAfterScrollStartedInScene_EdgeNoOverscroll_doNothing() = runGestureTest {
         flingAfterScrollStartedInScene(use = EdgeNoPreview, idleAfterScroll = true)
 
@@ -937,7 +987,7 @@
     }
 
     @Test
-    fun finish() = runGestureTest {
+    fun freezeAndAnimateToCurrentState() = runGestureTest {
         // Start at scene C.
         navigateToSceneC()
 
@@ -949,35 +999,25 @@
         // The current transition can be intercepted.
         assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
 
-        // Finish the transition.
+        // Freeze the transition.
         val transition = transitionState as Transition
-        val job = transition.finish()
+        transition.freezeAndAnimateToCurrentState()
         assertTransition(isUserInputOngoing = false)
-
-        // The current transition can not be intercepted anymore.
-        assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isFalse()
-
-        // Calling finish() multiple times returns the same Job.
-        assertThat(transition.finish()).isSameInstanceAs(job)
-        assertThat(transition.finish()).isSameInstanceAs(job)
-        assertThat(transition.finish()).isSameInstanceAs(job)
-
-        // We can join the job to wait for the animation to end.
-        assertTransition()
-        job.join()
+        advanceUntilIdle()
         assertIdle(SceneC)
     }
 
     @Test
-    fun finish_cancelled() = runGestureTest {
-        // Swipe up from the middle to transition to scene B.
-        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
-        onDragStarted(startedPosition = middle, overSlop = up(0.1f))
-        assertTransition(fromScene = SceneA, toScene = SceneB)
+    fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
+        assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
+        onDragStarted(overSlop = up(0.1f))
+        assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
 
-        // Finish the transition and cancel the returned job.
-        (transitionState as Transition).finish().cancelAndJoin()
-        assertIdle(SceneA)
+        layoutState.startTransitionImmediately(
+            animationScope = testScope.backgroundScope,
+            transition(SceneA, SceneB)
+        )
+        assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
     }
 
     @Test
@@ -1035,7 +1075,7 @@
 
         // We scrolled down, under scene C there is nothing, so we can use the overscroll spec
         assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNotNull()
-        assertThat(layoutState.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneC)
+        assertThat(layoutState.currentTransition?.currentOverscrollSpec?.content).isEqualTo(SceneC)
         val transition = layoutState.currentTransition
         assertThat(transition).isNotNull()
         assertThat(transition!!.progress).isEqualTo(-0.1f)
@@ -1090,7 +1130,7 @@
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
 
         val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.5f))
-        val transition = assertThat(transitionState).isTransition()
+        val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasProgress(0.5f)
@@ -1116,7 +1156,7 @@
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
 
         val dragController = onDragStarted(startedPosition = middle, overSlop = down(0.5f))
-        val transition = assertThat(transitionState).isTransition()
+        val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneC)
         assertThat(transition).hasProgress(0.5f)
@@ -1142,7 +1182,7 @@
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
 
         val dragController = onDragStarted(startedPosition = middle, overSlop = up(1.5f))
-        val transition = assertThat(transitionState).isTransition()
+        val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasProgress(1.5f)
@@ -1169,7 +1209,7 @@
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
 
         val dragController = onDragStarted(startedPosition = middle, overSlop = down(1.5f))
-        val transition = assertThat(transitionState).isTransition()
+        val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneC)
         assertThat(transition).hasProgress(1.5f)
@@ -1197,7 +1237,7 @@
 
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
         val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f))
-        val transition = assertThat(transitionState).isTransition()
+        val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasProgress(-1f)
@@ -1225,7 +1265,7 @@
 
         val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
         val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f))
-        val transition = assertThat(transitionState).isTransition()
+        val transition = assertThat(transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneC)
         assertThat(transition).hasProgress(-1f)
@@ -1282,13 +1322,96 @@
     @Test
     fun interceptingTransitionReplacesCurrentTransition() = runGestureTest {
         val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.5f))
-        val transition = assertThat(layoutState.transitionState).isTransition()
+        val transition = assertThat(layoutState.transitionState).isSceneTransition()
         controller.onDragStopped(velocity = 0f)
 
         // Intercept the transition.
         onDragStartedImmediately()
-        val newTransition = assertThat(layoutState.transitionState).isTransition()
+        val newTransition = assertThat(layoutState.transitionState).isSceneTransition()
         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/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 20b9b49..60596de 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -71,10 +71,11 @@
 import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.setContentAndCreateMainScope
+import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Ignore
 import org.junit.Rule
@@ -504,7 +505,7 @@
     }
 
     @Test
-    fun elementModifierNodeIsRecycledInLazyLayouts() = runTest {
+    fun elementModifierNodeIsRecycledInLazyLayouts() {
         val nPages = 2
         val pagerState = PagerState(currentPage = 0) { nPages }
         var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -630,18 +631,19 @@
                 )
             }
 
-        rule.setContent {
-            SceneTransitionLayout(state) {
-                scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
-                scene(SceneB) {}
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) }
+                    scene(SceneB) {}
+                }
             }
-        }
 
         // Pause the clock to block recompositions.
         rule.mainClock.autoAdvance = false
 
         // Change the current transition.
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(transition(from = SceneA, to = SceneB, progress = { 0.5f }))
         }
 
@@ -724,6 +726,7 @@
                 layoutHeight = layoutHeight,
                 sceneTransitions = {
                     overscroll(SceneB, Orientation.Vertical) {
+                        progressConverter = ProgressConverter.linear()
                         // On overscroll 100% -> Foo should translate by overscrollTranslateY
                         translate(TestElements.Foo, y = overscrollTranslateY)
                     }
@@ -735,7 +738,7 @@
 
         val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag)
         fooElement.assertTopPositionInRootIsEqualTo(0.dp)
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).isNotNull()
         assertThat(transition).hasProgress(0.5f)
         assertThat(animatedFloat).isEqualTo(50f)
@@ -780,6 +783,7 @@
                     transitions =
                         transitions {
                             overscroll(SceneB, Orientation.Vertical) {
+                                progressConverter = ProgressConverter.linear()
                                 translate(TestElements.Foo, y = overscrollTranslateY)
                             }
                         }
@@ -822,7 +826,7 @@
             moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
         }
 
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasOverscrollSpec()
         assertThat(transition).hasProgress(-0.5f)
         fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f)
@@ -905,7 +909,7 @@
             }
         }
 
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(0.5f)
         fooElement.assertTopPositionInRootIsEqualTo(translateY * 0.5f)
     }
@@ -921,6 +925,7 @@
                 layoutHeight = layoutHeight,
                 sceneTransitions = {
                     overscroll(SceneB, Orientation.Vertical) {
+                        progressConverter = ProgressConverter.linear()
                         // On overscroll 100% -> Foo should translate by layoutHeight
                         translate(TestElements.Foo, y = { absoluteDistance })
                     }
@@ -939,7 +944,7 @@
             moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
         }
 
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(animatedFloat).isEqualTo(100f)
 
         // Scroll 150% (100% scroll + 50% overscroll)
@@ -992,7 +997,7 @@
             moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
         }
 
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(animatedFloat).isEqualTo(100f)
 
         // Scroll 200% (100% scroll + 100% overscroll)
@@ -1015,7 +1020,7 @@
                 layoutHeight = layoutHeight,
                 sceneTransitions = {
                     // Overscroll progress will be linear (by default)
-                    defaultOverscrollProgressConverter = ProgressConverter { it }
+                    defaultOverscrollProgressConverter = ProgressConverter.linear()
 
                     overscroll(SceneB, Orientation.Vertical) {
                         // This override the defaultOverscrollProgressConverter
@@ -1039,7 +1044,7 @@
             moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
         }
 
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(animatedFloat).isEqualTo(100f)
 
         // Scroll 200% (100% scroll + 100% overscroll)
@@ -1083,7 +1088,7 @@
             moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
         }
 
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(animatedFloat).isEqualTo(100f)
 
         // Scroll 200% (100% scroll + 100% overscroll)
@@ -1125,6 +1130,7 @@
                         )
 
                     overscroll(SceneB, Orientation.Vertical) {
+                        progressConverter = ProgressConverter.linear()
                         // On overscroll 100% -> Foo should translate by layoutHeight
                         translate(TestElements.Foo, y = { absoluteDistance })
                     }
@@ -1143,7 +1149,7 @@
             moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
         }
 
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
 
         // Scroll 150% (100% scroll + 50% overscroll)
         assertThat(transition).hasProgress(1.5f)
@@ -1160,7 +1166,7 @@
 
         assertThat(transition.progress).isLessThan(1f)
         assertThat(transition).hasOverscrollSpec()
-        assertThat(transition).hasBouncingScene(transition.toScene)
+        assertThat(transition).hasBouncingContent(transition.toContent)
         assertThat(animatedFloat).isEqualTo(100f)
     }
 
@@ -1243,13 +1249,15 @@
 
         val transitions = state.currentTransitions
         assertThat(transitions).hasSize(2)
-        assertThat(transitions[0]).hasFromScene(SceneA)
-        assertThat(transitions[0]).hasToScene(SceneB)
-        assertThat(transitions[0]).hasProgress(0f)
+        val firstTransition = assertThat(transitions[0]).isSceneTransition()
+        assertThat(firstTransition).hasFromScene(SceneA)
+        assertThat(firstTransition).hasToScene(SceneB)
+        assertThat(firstTransition).hasProgress(0f)
 
-        assertThat(transitions[1]).hasFromScene(SceneB)
-        assertThat(transitions[1]).hasToScene(SceneC)
-        assertThat(transitions[1]).hasProgress(0f)
+        val secondTransition = assertThat(transitions[1]).isSceneTransition()
+        assertThat(secondTransition).hasFromScene(SceneB)
+        assertThat(secondTransition).hasToScene(SceneC)
+        assertThat(secondTransition).hasProgress(0f)
 
         // First frame: both are at x = 0dp. For the whole transition, Foo is at y = 0dp and Bar is
         // at y = layoutSize - elementSoze = 100dp.
@@ -1290,7 +1298,7 @@
     }
 
     @Test
-    fun interruption() = runTest {
+    fun interruption() {
         // 4 frames of animation.
         val duration = 4 * 16
 
@@ -1330,37 +1338,41 @@
         val valueInC = 200f
 
         lateinit var layoutImpl: SceneTransitionLayoutImpl
-        rule.setContent {
-            SceneTransitionLayoutForTesting(
-                state,
-                Modifier.size(layoutSize),
-                onLayoutImpl = { layoutImpl = it },
-            ) {
-                // In scene A, Foo is aligned at the TopStart.
-                scene(SceneA) {
-                    Box(Modifier.fillMaxSize()) {
-                        Foo(sizeInA, valueInA, Modifier.align(Alignment.TopStart))
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayoutForTesting(
+                    state,
+                    Modifier.size(layoutSize),
+                    onLayoutImpl = { layoutImpl = it },
+                ) {
+                    // In scene A, Foo is aligned at the TopStart.
+                    scene(SceneA) {
+                        Box(Modifier.fillMaxSize()) {
+                            Foo(sizeInA, valueInA, Modifier.align(Alignment.TopStart))
+                        }
                     }
-                }
 
-                // In scene C, Foo is aligned at the BottomEnd, so it moves vertically when coming
-                // from B. We put it before (below) scene B so that we can check that interruptions
-                // values and deltas are properly cleared once all transitions are done.
-                scene(SceneC) {
-                    Box(Modifier.fillMaxSize()) {
-                        Foo(sizeInC, valueInC, Modifier.align(Alignment.BottomEnd))
+                    // In scene C, Foo is aligned at the BottomEnd, so it moves vertically when
+                    // coming
+                    // from B. We put it before (below) scene B so that we can check that
+                    // interruptions
+                    // values and deltas are properly cleared once all transitions are done.
+                    scene(SceneC) {
+                        Box(Modifier.fillMaxSize()) {
+                            Foo(sizeInC, valueInC, Modifier.align(Alignment.BottomEnd))
+                        }
                     }
-                }
 
-                // In scene B, Foo is aligned at the TopEnd, so it moves horizontally when coming
-                // from A.
-                scene(SceneB) {
-                    Box(Modifier.fillMaxSize()) {
-                        Foo(sizeInB, valueInB, Modifier.align(Alignment.TopEnd))
+                    // In scene B, Foo is aligned at the TopEnd, so it moves horizontally when
+                    // coming
+                    // from A.
+                    scene(SceneB) {
+                        Box(Modifier.fillMaxSize()) {
+                            Foo(sizeInB, valueInB, Modifier.align(Alignment.TopEnd))
+                        }
                     }
                 }
             }
-        }
 
         // The offset of Foo when idle in A, B or C.
         val offsetInA = DpOffset.Zero
@@ -1384,12 +1396,12 @@
                 from = SceneA,
                 to = SceneB,
                 progress = { aToBProgress },
-                onFinish = neverFinish(),
+                onFreezeAndAnimate = { /* never finish */ },
             )
         val offsetInAToB = lerp(offsetInA, offsetInB, aToBProgress)
         val sizeInAToB = lerp(sizeInA, sizeInB, aToBProgress)
         val valueInAToB = lerp(valueInA, valueInB, aToBProgress)
-        rule.runOnUiThread { state.startTransition(aToB) }
+        scope.launch { state.startTransition(aToB) }
         rule
             .onNode(isElement(TestElements.Foo, SceneB))
             .assertSizeIsEqualTo(sizeInAToB)
@@ -1409,7 +1421,7 @@
                 progress = { bToCProgress },
                 interruptionProgress = { interruptionProgress },
             )
-        rule.runOnUiThread { state.startTransition(bToC) }
+        scope.launch { state.startTransition(bToC) }
 
         // The interruption deltas, which will be multiplied by the interruption progress then added
         // to the current transition offset and size.
@@ -1470,10 +1482,8 @@
             .assertSizeIsEqualTo(sizeInC)
 
         // Manually finish the transition.
-        rule.runOnUiThread {
-            state.finishTransition(aToB)
-            state.finishTransition(bToC)
-        }
+        aToB.finish()
+        bToC.finish()
         rule.waitForIdle()
         assertThat(state.transitionState).isIdle()
 
@@ -1492,7 +1502,7 @@
     }
 
     @Test
-    fun interruption_sharedTransitionDisabled() = runTest {
+    fun interruption_sharedTransitionDisabled() {
         // 4 frames of animation.
         val duration = 4 * 16
         val layoutSize = DpSize(200.dp, 100.dp)
@@ -1518,21 +1528,22 @@
             Box(modifier.element(TestElements.Foo).size(fooSize))
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state, Modifier.size(layoutSize)) {
-                scene(SceneA) {
-                    Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopStart)) }
-                }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+                    scene(SceneA) {
+                        Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopStart)) }
+                    }
 
-                scene(SceneB) {
-                    Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopEnd)) }
-                }
+                    scene(SceneB) {
+                        Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopEnd)) }
+                    }
 
-                scene(SceneC) {
-                    Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.BottomEnd)) }
+                    scene(SceneC) {
+                        Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.BottomEnd)) }
+                    }
                 }
             }
-        }
 
         // The offset of Foo when idle in A, B or C.
         val offsetInA = DpOffset.Zero
@@ -1541,7 +1552,12 @@
 
         // State is a transition A => B at 50% interrupted by B => C at 30%.
         val aToB =
-            transition(from = SceneA, to = SceneB, progress = { 0.5f }, onFinish = neverFinish())
+            transition(
+                from = SceneA,
+                to = SceneB,
+                progress = { 0.5f },
+                onFreezeAndAnimate = { /* never finish */ },
+            )
         var bToCInterruptionProgress by mutableStateOf(1f)
         val bToC =
             transition(
@@ -1549,11 +1565,11 @@
                 to = SceneC,
                 progress = { 0.3f },
                 interruptionProgress = { bToCInterruptionProgress },
-                onFinish = neverFinish(),
+                onFreezeAndAnimate = { /* never finish */ },
             )
-        rule.runOnUiThread { state.startTransition(aToB) }
+        scope.launch { state.startTransition(aToB) }
         rule.waitForIdle()
-        rule.runOnUiThread { state.startTransition(bToC) }
+        scope.launch { state.startTransition(bToC) }
 
         // Foo is placed in both B and C given that the shared transition is disabled. In B, its
         // offset is impacted by the interruption but in C it is not.
@@ -1573,7 +1589,8 @@
 
         // Manually finish A => B so only B => C is remaining.
         bToCInterruptionProgress = 0f
-        rule.runOnUiThread { state.finishTransition(aToB) }
+        aToB.finish()
+
         rule
             .onNode(isElement(TestElements.Foo, SceneB))
             .assertPositionInRootIsEqualTo(offsetInB.x, offsetInB.y)
@@ -1589,7 +1606,7 @@
                 progress = { 0.7f },
                 interruptionProgress = { 1f },
             )
-        rule.runOnUiThread { state.startTransition(bToA) }
+        scope.launch { state.startTransition(bToA) }
 
         // Foo should have the position it had in B right before the interruption.
         rule
@@ -1603,32 +1620,35 @@
         val state =
             rule.runOnUiThread {
                 MutableSceneTransitionLayoutStateImpl(
-                        SceneA,
-                        transitions { overscrollDisabled(SceneA, Orientation.Horizontal) }
-                    )
-                    .apply {
-                        startTransition(
-                            transition(
-                                from = SceneA,
-                                to = SceneB,
-                                progress = { -1f },
-                                orientation = Orientation.Horizontal
-                            )
-                        )
-                    }
+                    SceneA,
+                    transitions { overscrollDisabled(SceneA, Orientation.Horizontal) }
+                )
             }
 
         lateinit var layoutImpl: SceneTransitionLayoutImpl
-        rule.setContent {
-            SceneTransitionLayoutForTesting(
-                state,
-                Modifier.size(100.dp),
-                onLayoutImpl = { layoutImpl = it },
-            ) {
-                scene(SceneA) {}
-                scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayoutForTesting(
+                    state,
+                    Modifier.size(100.dp),
+                    onLayoutImpl = { layoutImpl = it },
+                ) {
+                    scene(SceneA) {}
+                    scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+                }
             }
+
+        scope.launch {
+            state.startTransition(
+                transition(
+                    from = SceneA,
+                    to = SceneB,
+                    progress = { -1f },
+                    orientation = Orientation.Horizontal
+                )
+            )
         }
+        rule.waitForIdle()
 
         assertThat(layoutImpl.elements).containsKey(TestElements.Foo)
         val foo = layoutImpl.elements.getValue(TestElements.Foo)
@@ -1641,7 +1661,7 @@
     }
 
     @Test
-    fun lastAlphaIsNotSetByOutdatedLayer() = runTest {
+    fun lastAlphaIsNotSetByOutdatedLayer() {
         val state =
             rule.runOnUiThread {
                 MutableSceneTransitionLayoutStateImpl(
@@ -1651,23 +1671,24 @@
             }
 
         lateinit var layoutImpl: SceneTransitionLayoutImpl
-        rule.setContent {
-            SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
-                scene(SceneA) {}
-                scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
-                scene(SceneC) { Box(Modifier.element(TestElements.Foo)) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+                    scene(SceneA) {}
+                    scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+                    scene(SceneC) { Box(Modifier.element(TestElements.Foo)) }
+                }
             }
-        }
 
         // Start A => B at 0.5f.
         var aToBProgress by mutableStateOf(0.5f)
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(
                 transition(
                     from = SceneA,
                     to = SceneB,
                     progress = { aToBProgress },
-                    onFinish = neverFinish(),
+                    onFreezeAndAnimate = { /* never finish */ },
                 )
             )
         }
@@ -1686,7 +1707,7 @@
         assertThat(fooInB.lastAlpha).isEqualTo(0.7f)
 
         // Start B => C at 0.3f.
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(transition(from = SceneB, to = SceneC, progress = { 0.3f }))
         }
         rule.waitForIdle()
@@ -1714,16 +1735,17 @@
             }
 
         lateinit var layoutImpl: SceneTransitionLayoutImpl
-        rule.setContent {
-            SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
-                scene(SceneA) {}
-                scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+                    scene(SceneA) {}
+                    scene(SceneB) { Box(Modifier.element(TestElements.Foo)) }
+                }
             }
-        }
 
         // Start A => B at 60%.
         var interruptionProgress by mutableStateOf(1f)
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(
                 transition(
                     from = SceneA,
@@ -1768,19 +1790,20 @@
             Box(Modifier.element(TestElements.Foo).size(10.dp))
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state) {
-                scene(SceneA) { Foo() }
-                scene(SceneB) { Foo() }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    scene(SceneA) { Foo() }
+                    scene(SceneB) { Foo() }
+                }
             }
-        }
 
         rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsDisplayed()
         rule.onNode(isElement(TestElements.Foo, SceneB)).assertDoesNotExist()
 
         // A => B while overscrolling at scene B.
         var progress by mutableStateOf(2f)
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress }))
         }
         rule.waitForIdle()
@@ -1821,19 +1844,20 @@
             MovableElement(key, modifier) { content { Text(text) } }
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state) {
-                scene(SceneA) { MovableFoo(text = fooInA) }
-                scene(SceneB) { MovableFoo(text = fooInB) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    scene(SceneA) { MovableFoo(text = fooInA) }
+                    scene(SceneB) { MovableFoo(text = fooInB) }
+                }
             }
-        }
 
         rule.onNode(hasText(fooInA)).assertIsDisplayed()
         rule.onNode(hasText(fooInB)).assertDoesNotExist()
 
         // A => B while overscrolling at scene B.
         var progress by mutableStateOf(2f)
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress }))
         }
         rule.waitForIdle()
@@ -1852,13 +1876,14 @@
     }
 
     @Test
-    fun interruptionThenOverscroll() = runTest {
+    fun interruptionThenOverscroll() {
         val state =
             rule.runOnUiThread {
                 MutableSceneTransitionLayoutStateImpl(
                     SceneA,
                     transitions {
                         overscroll(SceneB, Orientation.Vertical) {
+                            progressConverter = ProgressConverter.linear()
                             translate(TestElements.Foo, y = 15.dp)
                         }
                     }
@@ -1872,22 +1897,23 @@
             }
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state, Modifier.size(200.dp)) {
-                scene(SceneA) { SceneWithFoo(offset = DpOffset.Zero) }
-                scene(SceneB) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 0.dp)) }
-                scene(SceneC) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 40.dp)) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state, Modifier.size(200.dp)) {
+                    scene(SceneA) { SceneWithFoo(offset = DpOffset.Zero) }
+                    scene(SceneB) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 0.dp)) }
+                    scene(SceneC) { SceneWithFoo(offset = DpOffset(x = 40.dp, y = 40.dp)) }
+                }
             }
-        }
 
         // Start A => B at 75%.
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(
                 transition(
                     from = SceneA,
                     to = SceneB,
                     progress = { 0.75f },
-                    onFinish = neverFinish(),
+                    onFreezeAndAnimate = { /* never finish */ },
                 )
             )
         }
@@ -1900,7 +1926,7 @@
         // Interrupt A => B with B => C at 0%.
         var progress by mutableStateOf(0f)
         var interruptionProgress by mutableStateOf(1f)
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(
                 transition(
                     from = SceneB,
@@ -1908,7 +1934,7 @@
                     progress = { progress },
                     interruptionProgress = { interruptionProgress },
                     orientation = Orientation.Vertical,
-                    onFinish = neverFinish(),
+                    onFreezeAndAnimate = { /* never finish */ },
                 )
             )
         }
@@ -1956,12 +1982,13 @@
         }
 
         lateinit var layoutImpl: SceneTransitionLayoutImpl
-        rule.setContent {
-            SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
-                scene(SceneA) { NestedFooBar() }
-                scene(SceneB) { NestedFooBar() }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+                    scene(SceneA) { NestedFooBar() }
+                    scene(SceneB) { NestedFooBar() }
+                }
             }
-        }
 
         // Idle on A: composed and placed only in B.
         rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsDisplayed()
@@ -1990,7 +2017,7 @@
         assertThat(barInA.lastScale).isNotEqualTo(Scale.Unspecified)
 
         // A => B: composed in both and placed only in B.
-        rule.runOnUiThread { state.startTransition(transition(from = SceneA, to = SceneB)) }
+        scope.launch { state.startTransition(transition(from = SceneA, to = SceneB)) }
         rule.onNode(isElement(TestElements.Foo, SceneA)).assertExists().assertIsNotDisplayed()
         rule.onNode(isElement(TestElements.Bar, SceneA)).assertExists().assertIsNotDisplayed()
         rule.onNode(isElement(TestElements.Foo, SceneB)).assertIsDisplayed()
@@ -2017,7 +2044,7 @@
     }
 
     @Test
-    fun currentTransitionSceneIsUsedToComputeElementValues() = runTest {
+    fun currentTransitionSceneIsUsedToComputeElementValues() {
         val state =
             rule.runOnIdle {
                 MutableSceneTransitionLayoutStateImpl(
@@ -2037,23 +2064,31 @@
             }
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state, Modifier.size(200.dp)) {
-                scene(SceneA) { Foo() }
-                scene(SceneB) {}
-                scene(SceneC) { Foo() }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state, Modifier.size(200.dp)) {
+                    scene(SceneA) { Foo() }
+                    scene(SceneB) {}
+                    scene(SceneC) { Foo() }
+                }
             }
-        }
 
         // We have 2 transitions:
         //  - A => B at 100%
         //  - B => C at 0%
         // So Foo should have a size of (40dp, 60dp) in both A and C given that it is scaling its
         // size in B => C.
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(
-                transition(from = SceneA, to = SceneB, progress = { 1f }, onFinish = neverFinish())
+                transition(
+                    from = SceneA,
+                    to = SceneB,
+                    progress = { 1f },
+                    onFreezeAndAnimate = { /* never finish */ },
+                )
             )
+        }
+        scope.launch {
             state.startTransition(transition(from = SceneB, to = SceneC, progress = { 0f }))
         }
 
@@ -2062,7 +2097,7 @@
     }
 
     @Test
-    fun interruptionDeltasAreProperlyCleaned() = runTest {
+    fun interruptionDeltasAreProperlyCleaned() {
         val state = rule.runOnIdle { MutableSceneTransitionLayoutStateImpl(SceneA) }
 
         @Composable
@@ -2072,18 +2107,24 @@
             }
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state, Modifier.size(200.dp)) {
-                scene(SceneA) { Foo(offset = 0.dp) }
-                scene(SceneB) { Foo(offset = 20.dp) }
-                scene(SceneC) { Foo(offset = 40.dp) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state, Modifier.size(200.dp)) {
+                    scene(SceneA) { Foo(offset = 0.dp) }
+                    scene(SceneB) { Foo(offset = 20.dp) }
+                    scene(SceneC) { Foo(offset = 40.dp) }
+                }
             }
-        }
 
         // Start A => B at 50%.
         val aToB =
-            transition(from = SceneA, to = SceneB, progress = { 0.5f }, onFinish = neverFinish())
-        rule.runOnUiThread { state.startTransition(aToB) }
+            transition(
+                from = SceneA,
+                to = SceneB,
+                progress = { 0.5f },
+                onFreezeAndAnimate = { /* never finish */ },
+            )
+        scope.launch { state.startTransition(aToB) }
         rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(10.dp, 10.dp)
 
         // Start B => C at 0%. This will compute an interruption delta of (-10dp, -10dp) so that the
@@ -2096,9 +2137,9 @@
                 current = { SceneB },
                 progress = { 0f },
                 interruptionProgress = { interruptionProgress },
-                onFinish = neverFinish(),
+                onFreezeAndAnimate = { /* never finish */ },
             )
-        rule.runOnUiThread { state.startTransition(bToC) }
+        scope.launch { state.startTransition(bToC) }
         rule.onNode(isElement(TestElements.Foo, SceneC)).assertPositionInRootIsEqualTo(10.dp, 10.dp)
 
         // Finish the interruption and leave the transition progress at 0f. We should be at the same
@@ -2109,9 +2150,9 @@
         // Finish both transitions but directly start a new one B => A with interruption progress
         // 100%. We should be at (20dp, 20dp), unless the interruption deltas have not been
         // correctly cleaned.
-        rule.runOnUiThread {
-            state.finishTransition(aToB)
-            state.finishTransition(bToC)
+        aToB.finish()
+        bToC.finish()
+        scope.launch {
             state.startTransition(
                 transition(
                     from = SceneB,
@@ -2125,7 +2166,7 @@
     }
 
     @Test
-    fun lastSizeIsUnspecifiedWhenOverscrollingOtherScene() = runTest {
+    fun lastSizeIsUnspecifiedWhenOverscrollingOtherScene() {
         val state =
             rule.runOnIdle {
                 MutableSceneTransitionLayoutStateImpl(
@@ -2140,17 +2181,23 @@
         }
 
         lateinit var layoutImpl: SceneTransitionLayoutImpl
-        rule.setContent {
-            SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
-                scene(SceneA) { Foo() }
-                scene(SceneB) { Foo() }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+                    scene(SceneA) { Foo() }
+                    scene(SceneB) { Foo() }
+                }
             }
-        }
 
         // Overscroll A => B on A.
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(
-                transition(from = SceneA, to = SceneB, progress = { -1f }, onFinish = neverFinish())
+                transition(
+                    from = SceneA,
+                    to = SceneB,
+                    progress = { -1f },
+                    onFreezeAndAnimate = { /* never finish */ },
+                )
             )
         }
         rule.waitForIdle()
@@ -2166,7 +2213,7 @@
     }
 
     @Test
-    fun transparentElementIsNotImpactingInterruption() = runTest {
+    fun transparentElementIsNotImpactingInterruption() {
         val state =
             rule.runOnIdle {
                 MutableSceneTransitionLayoutStateImpl(
@@ -2193,23 +2240,24 @@
             Box(modifier.element(TestElements.Foo).size(10.dp))
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state) {
-                scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
 
-                // Define A after B so that Foo is placed in A during A <=> B.
-                scene(SceneA) { Foo() }
+                    // Define A after B so that Foo is placed in A during A <=> B.
+                    scene(SceneA) { Foo() }
+                }
             }
-        }
 
         // Start A => B at 70%.
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(
                 transition(
                     from = SceneA,
                     to = SceneB,
                     progress = { 0.7f },
-                    onFinish = neverFinish(),
+                    onFreezeAndAnimate = { /* never finish */ },
                 )
             )
         }
@@ -2220,14 +2268,14 @@
         // Start B => A at 50% with interruptionProgress = 100%. Foo is placed in A and should still
         // be at (40dp, 60dp) given that it was fully transparent in A before the interruption.
         var interruptionProgress by mutableStateOf(1f)
-        rule.runOnUiThread {
+        scope.launch {
             state.startTransition(
                 transition(
                     from = SceneB,
                     to = SceneA,
                     progress = { 0.5f },
                     interruptionProgress = { interruptionProgress },
-                    onFinish = neverFinish(),
+                    onFreezeAndAnimate = { /* never finish */ },
                 )
             )
         }
@@ -2243,7 +2291,7 @@
     }
 
     @Test
-    fun replacedTransitionDoesNotTriggerInterruption() = runTest {
+    fun replacedTransitionDoesNotTriggerInterruption() {
         val state = rule.runOnIdle { MutableSceneTransitionLayoutStateImpl(SceneA) }
 
         @Composable
@@ -2251,17 +2299,23 @@
             Box(modifier.element(TestElements.Foo).size(10.dp))
         }
 
-        rule.setContent {
-            SceneTransitionLayout(state) {
-                scene(SceneA) { Foo() }
-                scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    scene(SceneA) { Foo() }
+                    scene(SceneB) { Foo(Modifier.offset(40.dp, 60.dp)) }
+                }
             }
-        }
 
         // Start A => B at 50%.
         val aToB1 =
-            transition(from = SceneA, to = SceneB, progress = { 0.5f }, onFinish = neverFinish())
-        rule.runOnUiThread { state.startTransition(aToB1) }
+            transition(
+                from = SceneA,
+                to = SceneB,
+                progress = { 0.5f },
+                onFreezeAndAnimate = { /* never finish */ },
+            )
+        scope.launch { state.startTransition(aToB1) }
         rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed()
         rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(20.dp, 30.dp)
 
@@ -2275,7 +2329,7 @@
                 interruptionProgress = { 1f },
                 replacedTransition = aToB1,
             )
-        rule.runOnUiThread { state.startTransition(aToB2) }
+        scope.launch { state.startTransition(aToB2) }
         rule.onNode(isElement(TestElements.Foo, SceneA)).assertIsNotDisplayed()
         rule.onNode(isElement(TestElements.Foo, SceneB)).assertPositionInRootIsEqualTo(40.dp, 60.dp)
     }
@@ -2421,12 +2475,13 @@
         }
 
         lateinit var layoutImpl: SceneTransitionLayoutImpl
-        rule.setContent {
-            SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
-                scene(from) { Box { exitingElements.forEach { Foo(it) } } }
-                scene(to) { Box { enteringElements.forEach { Foo(it) } } }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayoutForTesting(state, onLayoutImpl = { layoutImpl = it }) {
+                    scene(from) { Box { exitingElements.forEach { Foo(it) } } }
+                    scene(to) { Box { enteringElements.forEach { Foo(it) } } }
+                }
             }
-        }
 
         val bToA =
             transition(
@@ -2436,7 +2491,7 @@
                 previewProgress = { previewProgress },
                 isInPreviewStage = { isInPreviewStage }
             )
-        rule.runOnUiThread { state.startTransition(bToA) }
+        scope.launch { state.startTransition(bToA) }
         rule.waitForIdle()
         return layoutImpl
     }
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 ca72181..bc929bd 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
@@ -25,9 +25,9 @@
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.runMonotonicClockTest
+import com.android.compose.test.transition
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,8 +44,8 @@
                 transitions { /* default interruption handler */ },
             )
 
-        state.setTargetScene(SceneB, coroutineScope = this)
-        state.setTargetScene(SceneC, coroutineScope = this)
+        state.setTargetScene(SceneB, animationScope = this)
+        state.setTargetScene(SceneC, animationScope = this)
 
         assertThat(state.currentTransitions)
             .comparingElementsUsing(FromToCurrentTriple)
@@ -69,7 +69,7 @@
                     interruptionHandler =
                         object : InterruptionHandler {
                             override fun onInterruption(
-                                interrupted: TransitionState.Transition,
+                                interrupted: TransitionState.Transition.ChangeScene,
                                 newTargetScene: SceneKey
                             ): InterruptionResult {
                                 return InterruptionResult(
@@ -81,8 +81,8 @@
                 },
             )
 
-        state.setTargetScene(SceneB, coroutineScope = this)
-        state.setTargetScene(SceneC, coroutineScope = this)
+        state.setTargetScene(SceneB, animationScope = this)
+        state.setTargetScene(SceneC, animationScope = this)
 
         assertThat(state.currentTransitions)
             .comparingElementsUsing(FromToCurrentTriple)
@@ -104,7 +104,7 @@
                     interruptionHandler =
                         object : InterruptionHandler {
                             override fun onInterruption(
-                                interrupted: TransitionState.Transition,
+                                interrupted: TransitionState.Transition.ChangeScene,
                                 newTargetScene: SceneKey
                             ): InterruptionResult {
                                 return InterruptionResult(
@@ -124,10 +124,10 @@
 
         // Animate to B and advance the transition a little bit so that progress > visibility
         // threshold and that reversing from B back to A won't immediately snap to A.
-        state.setTargetScene(SceneB, coroutineScope = this)
+        state.setTargetScene(SceneB, animationScope = this)
         testScheduler.advanceTimeBy(duration / 2L)
 
-        state.setTargetScene(SceneC, coroutineScope = this)
+        state.setTargetScene(SceneC, animationScope = this)
 
         assertThat(state.currentTransitions)
             .comparingElementsUsing(FromToCurrentTriple)
@@ -155,13 +155,21 @@
                 // Progress must be > visibility threshold otherwise we will directly snap to A.
                 progress = { 0.5f },
                 progressVelocity = { progressVelocity },
-                onFinish = { launch {} },
             )
-        state.startTransition(aToB)
+        state.startTransitionImmediately(animationScope = backgroundScope, aToB)
 
         // Animate back to A. The previous transition is reversed, i.e. it has the same (from, to)
         // pair, and its velocity is used when animating the progress back to 0.
-        val bToA = checkNotNull(state.setTargetScene(SceneA, coroutineScope = this))
+        val bToA =
+            checkNotNull(
+                    state.setTargetScene(
+                        SceneA,
+                        // We use testScope here and not backgroundScope because setTargetScene
+                        // needs the monotonic clock that is only available in the test scope.
+                        animationScope = this,
+                    )
+                )
+                .first
         testScheduler.runCurrent()
         assertThat(bToA).hasFromScene(SceneA)
         assertThat(bToA).hasToScene(SceneB)
@@ -181,13 +189,21 @@
                 to = SceneB,
                 current = { SceneA },
                 progressVelocity = { progressVelocity },
-                onFinish = { launch {} },
             )
-        state.startTransition(aToB)
+        state.startTransitionImmediately(animationScope = backgroundScope, aToB)
 
         // Animate to B. The previous transition is reversed, i.e. it has the same (from, to) pair,
         // and its velocity is used when animating the progress to 1.
-        val bToA = checkNotNull(state.setTargetScene(SceneB, coroutineScope = this))
+        val bToA =
+            checkNotNull(
+                    state.setTargetScene(
+                        SceneB,
+                        // We use testScope here and not backgroundScope because setTargetScene
+                        // needs the monotonic clock that is only available in the test scope.
+                        animationScope = this,
+                    )
+                )
+                .first
         testScheduler.runCurrent()
         assertThat(bToA).hasFromScene(SceneA)
         assertThat(bToA).hasToScene(SceneB)
@@ -198,7 +214,7 @@
     companion object {
         val FromToCurrentTriple =
             Correspondence.transforming(
-                { transition: TransitionState.Transition? ->
+                { 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/MovableElementContentPickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt
index e1d0945..c8e7e65 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementContentPickerTest.kt
@@ -17,6 +17,7 @@
 package com.android.compose.animation.scene
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
 import org.junit.Test
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 520e759..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
@@ -45,7 +45,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
-import com.android.compose.animation.scene.content.state.ContentState
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.assertSizeIsEqualTo
@@ -107,7 +106,7 @@
                 rule
                     .onNode(
                         hasText("count: 3") and
-                            hasParent(isElement(TestElements.Foo, scene = SceneA))
+                            hasParent(isElement(TestElements.Foo, content = SceneA))
                     )
                     .assertExists()
                     .assertIsNotDisplayed()
@@ -115,7 +114,7 @@
                 rule
                     .onNode(
                         hasText("count: 0") and
-                            hasParent(isElement(TestElements.Foo, scene = SceneB))
+                            hasParent(isElement(TestElements.Foo, content = SceneB))
                     )
                     .assertIsDisplayed()
                     .assertSizeIsEqualTo(75.dp, 75.dp)
@@ -160,11 +159,11 @@
 
                         override fun contentDuringTransition(
                             element: ElementKey,
-                            transition: ContentState.Transition<*>,
+                            transition: TransitionState.Transition,
                             fromContentZIndex: Float,
                             toContentZIndex: Float
                         ): ContentKey {
-                            transition as TransitionState.Transition
+                            transition as TransitionState.Transition.ChangeScene
                             assertThat(transition).hasFromScene(SceneA)
                             assertThat(transition).hasToScene(SceneB)
                             assertThat(fromContentZIndex).isEqualTo(0)
@@ -214,7 +213,7 @@
                 rule
                     .onNode(
                         hasText("count: 3") and
-                            hasParent(isElement(TestElements.Foo, scene = SceneA))
+                            hasParent(isElement(TestElements.Foo, content = SceneA))
                     )
                     .assertIsDisplayed()
                     .assertSizeIsEqualTo(75.dp, 75.dp)
@@ -235,7 +234,7 @@
                 rule
                     .onNode(
                         hasText("count: 3") and
-                            hasParent(isElement(TestElements.Foo, scene = SceneB))
+                            hasParent(isElement(TestElements.Foo, content = SceneB))
                     )
                     .assertIsDisplayed()
 
@@ -325,7 +324,7 @@
     fun movableElementScopeExtendsBoxScope() {
         val key = MovableElementKey("Foo", contents = setOf(SceneA))
         rule.setContent {
-            TestContentScope {
+            TestContentScope(currentScene = SceneA) {
                 MovableElement(key, Modifier.size(200.dp)) {
                     content {
                         Box(Modifier.testTag("bottomEnd").align(Alignment.BottomEnd))
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/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
index d8a06f5..d58a0a3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
@@ -125,7 +125,7 @@
 
         scrollUp(percent = 0.5f)
         // STL will start a transition with the remaining scroll
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(0.5f)
 
         scrollUp(percent = 1f)
@@ -133,33 +133,6 @@
     }
 
     @Test
-    fun customizeStlNestedScrollBehavior_DuringTransitionBetweenScenes() {
-        var canScroll = true
-        val state = setup2ScenesAndScrollTouchSlop {
-            Modifier.verticalNestedScrollToScene(
-                    bottomBehavior = NestedScrollBehavior.DuringTransitionBetweenScenes
-                )
-                .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
-        }
-
-        scrollUp(percent = 0.5f)
-        assertThat(state.transitionState).isIdle()
-
-        // Reach the end of the scrollable element
-        canScroll = false
-        scrollUp(percent = 0.5f)
-        assertThat(state.transitionState).isIdle()
-
-        pointerUp()
-        assertThat(state.transitionState).isIdle()
-
-        // Start a new gesture
-        pointerDownAndScrollTouchSlop()
-        scrollUp(percent = 0.5f)
-        assertThat(state.transitionState).isIdle()
-    }
-
-    @Test
     fun customizeStlNestedScrollBehavior_EdgeNoPreview() {
         var canScroll = true
         val state = setup2ScenesAndScrollTouchSlop {
@@ -183,7 +156,7 @@
         // Start a new gesture
         pointerDownAndScrollTouchSlop()
         scrollUp(percent = 0.5f)
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(0.5f)
 
         pointerUp()
@@ -208,7 +181,7 @@
         // Reach the end of the scrollable element
         canScroll = false
         scrollUp(percent = 0.5f)
-        val transition1 = assertThat(state.transitionState).isTransition()
+        val transition1 = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition1).hasProgress(0.5f)
 
         pointerUp()
@@ -219,7 +192,7 @@
         // Start a new gesture
         pointerDownAndScrollTouchSlop()
         scrollUp(percent = 0.5f)
-        val transition2 = assertThat(state.transitionState).isTransition()
+        val transition2 = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition2).hasProgress(0.5f)
 
         pointerUp()
@@ -242,7 +215,7 @@
         // Reach the end of the scrollable element
         canScroll = false
         scrollUp(percent = 0.5f)
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(0.5f)
 
         pointerUp()
@@ -253,20 +226,25 @@
 
     @Test
     fun customizeStlNestedScrollBehavior_multipleRequests() {
+        var canScroll = true
         val state = setup2ScenesAndScrollTouchSlop {
             Modifier
                 // This verticalNestedScrollToScene is closer the STL (an ancestor node)
                 .verticalNestedScrollToScene(bottomBehavior = NestedScrollBehavior.EdgeAlways)
                 // Another verticalNestedScrollToScene modifier
-                .verticalNestedScrollToScene(
-                    bottomBehavior = NestedScrollBehavior.DuringTransitionBetweenScenes
-                )
-                .scrollable(rememberScrollableState { 0f }, Vertical)
+                .verticalNestedScrollToScene(bottomBehavior = NestedScrollBehavior.EdgeNoPreview)
+                .scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
         }
 
         scrollUp(percent = 0.5f)
-        // EdgeAlways always consume the remaining scroll, DuringTransitionBetweenScenes does not.
-        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(state.transitionState).isIdle()
+
+        // Reach the end of the scrollable element
+        canScroll = false
+
+        scrollUp(percent = 0.5f)
+        // EdgeAlways always consume the remaining scroll, EdgeNoPreview does not.
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(0.5f)
     }
 
@@ -300,7 +278,7 @@
         scrollUp(percent = 0.2f)
 
         // STL can only start the transition if it has reset the amount of scroll consumed.
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(0.2f)
     }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 0543e7f..f3161f3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -29,6 +29,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.launch
@@ -89,16 +90,16 @@
             at(0) {
                 val state = observableState()
                 assertThat(state).isInstanceOf(ObservableTransitionState.Transition::class.java)
-                assertThat((state as ObservableTransitionState.Transition).fromScene)
+                assertThat((state as ObservableTransitionState.Transition).fromContent)
                     .isEqualTo(SceneA)
-                assertThat(state.toScene).isEqualTo(SceneB)
+                assertThat(state.toContent).isEqualTo(SceneB)
                 assertThat(state.progress()).isEqualTo(0f)
             }
             at(TestTransitionDuration / 2) {
                 val state = observableState()
-                assertThat((state as ObservableTransitionState.Transition).fromScene)
+                assertThat((state as ObservableTransitionState.Transition).fromContent)
                     .isEqualTo(SceneA)
-                assertThat(state.toScene).isEqualTo(SceneB)
+                assertThat(state.toContent).isEqualTo(SceneB)
                 assertThat(state.progress()).isEqualTo(0.5f)
             }
             after {
@@ -139,7 +140,7 @@
         var transitionCurrentScene by mutableStateOf(SceneA)
         val transition =
             transition(from = SceneA, to = SceneB, current = { transitionCurrentScene })
-        state.startTransition(transition)
+        state.startTransitionImmediately(animationScope = backgroundScope, transition)
         assertThat(currentScene.value).isEqualTo(SceneA)
 
         // Change the transition current scene.
@@ -199,7 +200,7 @@
 
         var state = observableState()
         assertThat(state).isInstanceOf(ObservableTransitionState.Transition::class.java)
-        assertThat((state as ObservableTransitionState.Transition).fromScene).isEqualTo(SceneA)
+        assertThat((state as ObservableTransitionState.Transition).fromContent).isEqualTo(SceneA)
         assertThat(state.previewProgress()).isEqualTo(0.4f)
         assertThat(state.isInPreviewStage()).isEqualTo(true)
 
@@ -217,7 +218,7 @@
         }
         state = observableState()
         assertThat(state).isInstanceOf(ObservableTransitionState.Transition::class.java)
-        assertThat((state as ObservableTransitionState.Transition).fromScene).isEqualTo(SceneA)
+        assertThat((state as ObservableTransitionState.Transition).fromContent).isEqualTo(SceneA)
         assertThat(state.previewProgress()).isEqualTo(0.4f)
         assertThat(state.isInPreviewStage()).isEqualTo(false)
 
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
new file mode 100644
index 0000000..471362b
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -0,0 +1,693 @@
+/*
+ * 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.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.hasTestTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+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.test.assertSizeIsEqualTo
+import com.android.compose.test.setContentAndCreateMainScope
+import com.android.compose.test.subjects.assertThat
+import com.android.compose.test.transition
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class OverlayTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Composable
+    private fun ContentScope.Foo(width: Dp = 100.dp, height: Dp = 100.dp) {
+        Box(Modifier.element(TestElements.Foo).size(width, height))
+    }
+
+    @Test
+    fun showThenHideOverlay() {
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        lateinit var coroutineScope: CoroutineScope
+        rule.setContent {
+            coroutineScope = rememberCoroutineScope()
+            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+                scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+                overlay(OverlayA) { Foo() }
+            }
+        }
+
+        // Initial state: overlay A is not shown, so Foo is displayed at the top left in scene A.
+        rule
+            .onNode(isElement(TestElements.Foo, content = SceneA))
+            .assertIsDisplayed()
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+        rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+
+        // Show overlay A: Foo is now centered on screen and placed in overlay A. It is not placed
+        // in scene A.
+        rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+        rule
+            .onNode(isElement(TestElements.Foo, content = SceneA))
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule
+            .onNode(isElement(TestElements.Foo, content = OverlayA))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+        // Hide overlay A: back to initial state, top-left in scene A.
+        rule.runOnUiThread { state.hideOverlay(OverlayA, coroutineScope) }
+        rule
+            .onNode(isElement(TestElements.Foo, content = SceneA))
+            .assertIsDisplayed()
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+        rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+    }
+
+    @Test
+    fun multipleOverlays() {
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        lateinit var coroutineScope: CoroutineScope
+        rule.setContent {
+            coroutineScope = rememberCoroutineScope()
+            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+                scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+                overlay(OverlayA) { Foo() }
+                overlay(OverlayB) { Foo() }
+            }
+        }
+
+        // Initial state.
+        rule
+            .onNode(isElement(TestElements.Foo, content = SceneA))
+            .assertIsDisplayed()
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+        rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+        rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+
+        // Show overlay A.
+        rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+        rule
+            .onNode(isElement(TestElements.Foo, content = SceneA))
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule
+            .onNode(isElement(TestElements.Foo, content = OverlayA))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+        rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+
+        // Replace overlay A by overlay B.
+        rule.runOnUiThread { state.replaceOverlay(OverlayA, OverlayB, coroutineScope) }
+        rule
+            .onNode(isElement(TestElements.Foo, content = SceneA))
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+        rule
+            .onNode(isElement(TestElements.Foo, content = OverlayB))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+        // Show overlay A: Foo is still placed in B because it has a higher zIndex, but it now
+        // exists in A as well.
+        rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+        rule
+            .onNode(isElement(TestElements.Foo, content = SceneA))
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule
+            .onNode(isElement(TestElements.Foo, content = OverlayA))
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule
+            .onNode(isElement(TestElements.Foo, content = OverlayB))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+        // Hide overlay B.
+        rule.runOnUiThread { state.hideOverlay(OverlayB, coroutineScope) }
+        rule
+            .onNode(isElement(TestElements.Foo, content = SceneA))
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule
+            .onNode(isElement(TestElements.Foo, content = OverlayA))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+        rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+
+        // Hide overlay A.
+        rule.runOnUiThread { state.hideOverlay(OverlayA, coroutineScope) }
+        rule
+            .onNode(isElement(TestElements.Foo, content = SceneA))
+            .assertIsDisplayed()
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+        rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+        rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+    }
+
+    @Test
+    fun movableElement() {
+        val key = MovableElementKey("MovableBar", contents = setOf(SceneA, OverlayA, OverlayB))
+        val elementChildTag = "elementChildTag"
+
+        fun elementChild(content: ContentKey) = hasTestTag(elementChildTag) and inContent(content)
+
+        @Composable
+        fun ContentScope.MovableBar() {
+            MovableElement(key, Modifier) {
+                content { Box(Modifier.testTag(elementChildTag).size(100.dp)) }
+            }
+        }
+
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        lateinit var coroutineScope: CoroutineScope
+        rule.setContent {
+            coroutineScope = rememberCoroutineScope()
+            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+                scene(SceneA) { Box(Modifier.fillMaxSize()) { MovableBar() } }
+                overlay(OverlayA) { MovableBar() }
+                overlay(OverlayB) { MovableBar() }
+            }
+        }
+
+        // Initial state.
+        rule
+            .onNode(elementChild(content = SceneA))
+            .assertIsDisplayed()
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+        rule.onNode(elementChild(content = OverlayA)).assertDoesNotExist()
+        rule.onNode(elementChild(content = OverlayB)).assertDoesNotExist()
+
+        // Show overlay A: movable element child only exists (is only composed) in overlay A.
+        rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+        rule.onNode(elementChild(content = SceneA)).assertDoesNotExist()
+        rule
+            .onNode(elementChild(content = OverlayA))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+        rule.onNode(elementChild(content = OverlayB)).assertDoesNotExist()
+
+        // Replace overlay A by overlay B: element child is only in overlay B.
+        rule.runOnUiThread { state.replaceOverlay(OverlayA, OverlayB, coroutineScope) }
+        rule.onNode(elementChild(content = SceneA)).assertDoesNotExist()
+        rule.onNode(elementChild(content = OverlayA)).assertDoesNotExist()
+        rule
+            .onNode(elementChild(content = OverlayB))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+        // Show overlay A: element child still only exists in overlay B because it has a higher
+        // zIndex.
+        rule.runOnUiThread { state.showOverlay(OverlayA, coroutineScope) }
+        rule.onNode(elementChild(content = SceneA)).assertDoesNotExist()
+        rule.onNode(elementChild(content = OverlayA)).assertDoesNotExist()
+        rule
+            .onNode(elementChild(content = OverlayB))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+        // Hide overlay B: element child is in overlay A.
+        rule.runOnUiThread { state.hideOverlay(OverlayB, coroutineScope) }
+        rule.onNode(elementChild(content = SceneA)).assertDoesNotExist()
+        rule
+            .onNode(elementChild(content = OverlayA))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+        rule.onNode(elementChild(content = OverlayB)).assertDoesNotExist()
+
+        // Hide overlay A: element child is in scene A.
+        rule.runOnUiThread { state.hideOverlay(OverlayA, coroutineScope) }
+        rule
+            .onNode(elementChild(content = SceneA))
+            .assertIsDisplayed()
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+        rule.onNode(elementChild(content = OverlayA)).assertDoesNotExist()
+        rule.onNode(elementChild(content = OverlayB)).assertDoesNotExist()
+    }
+
+    @Test
+    fun overlayAlignment() {
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+            }
+        var alignment by mutableStateOf(Alignment.Center)
+        rule.setContent {
+            SceneTransitionLayout(state, Modifier.size(200.dp)) {
+                scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+                overlay(OverlayA, alignment = alignment) { Foo() }
+            }
+        }
+
+        // Initial state: 100x100dp centered in 200x200dp layout.
+        rule
+            .onNode(isElement(TestElements.Foo, content = OverlayA))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+        // BottomStart.
+        alignment = Alignment.BottomStart
+        rule
+            .onNode(isElement(TestElements.Foo, content = OverlayA))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 100.dp)
+
+        // TopEnd.
+        alignment = Alignment.TopEnd
+        rule
+            .onNode(isElement(TestElements.Foo, content = OverlayA))
+            .assertSizeIsEqualTo(100.dp)
+            .assertPositionInRootIsEqualTo(100.dp, 0.dp)
+    }
+
+    @Test
+    fun overlayMaxSizeIsCurrentSceneSize() {
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+            }
+
+        val contentTag = "overlayContent"
+        rule.setContent {
+            SceneTransitionLayout(state) {
+                scene(SceneA) { Box(Modifier.size(100.dp)) { Foo() } }
+                overlay(OverlayA) { Box(Modifier.testTag(contentTag).fillMaxSize()) }
+            }
+        }
+
+        // Max overlay size is the size of the layout without overlays, not the (max) possible size
+        // of the layout.
+        rule.onNodeWithTag(contentTag).assertSizeIsEqualTo(100.dp)
+    }
+
+    @Test
+    fun showAnimation() {
+        rule.testShowOverlayTransition(
+            fromSceneContent = {
+                Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+                    Foo(width = 60.dp, height = 40.dp)
+                }
+            },
+            overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+            transition = {
+                // 4 frames of animation
+                spec = tween(4 * 16, easing = LinearEasing)
+            },
+        ) {
+            // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+            // size of 100x80dp, so at (40,20).
+            before {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertSizeIsEqualTo(60.dp, 40.dp)
+                    .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+            }
+
+            at(16) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(70.dp, 50.dp)
+                    .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+            }
+
+            at(32) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(80.dp, 60.dp)
+                    .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+            }
+
+            at(48) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(90.dp, 70.dp)
+                    .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+            }
+
+            after {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(100.dp, 80.dp)
+                    .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+            }
+        }
+    }
+
+    @Test
+    fun hideAnimation() {
+        rule.testHideOverlayTransition(
+            toSceneContent = {
+                Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+                    Foo(width = 60.dp, height = 40.dp)
+                }
+            },
+            overlayContent = { Foo(width = 100.dp, height = 80.dp) },
+            transition = {
+                // 4 frames of animation
+                spec = tween(4 * 16, easing = LinearEasing)
+            },
+        ) {
+            // Foo moves from centered (in a 180x120dp Box) with a size of 100x80dp, so at (40,20),
+            // to (0,0) with a size of 60x40dp.
+            before {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(100.dp, 80.dp)
+                    .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+            }
+
+            at(16) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(90.dp, 70.dp)
+                    .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+            }
+
+            at(32) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(80.dp, 60.dp)
+                    .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+            }
+
+            at(48) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(70.dp, 50.dp)
+                    .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+            }
+
+            after {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertSizeIsEqualTo(60.dp, 40.dp)
+                    .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+            }
+        }
+    }
+
+    @Test
+    fun replaceAnimation() {
+        rule.testReplaceOverlayTransition(
+            currentSceneContent = { Box(Modifier.size(width = 180.dp, height = 120.dp)) },
+            fromContent = { Foo(width = 60.dp, height = 40.dp) },
+            fromAlignment = Alignment.TopStart,
+            toContent = { Foo(width = 100.dp, height = 80.dp) },
+            transition = {
+                // 4 frames of animation
+                spec = tween(4 * 16, easing = LinearEasing)
+            },
+        ) {
+            // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+            // size of 100x80dp, so at (40,20).
+            before {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertSizeIsEqualTo(60.dp, 40.dp)
+                    .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+                rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+            }
+
+            at(16) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(70.dp, 50.dp)
+                    .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+            }
+
+            at(32) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(80.dp, 60.dp)
+                    .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+            }
+
+            at(48) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(90.dp, 70.dp)
+                    .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+            }
+
+            after {
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(100.dp, 80.dp)
+                    .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+            }
+        }
+    }
+
+    @Test
+    fun replaceAnimation_elementInCurrentSceneAndOneOverlay() {
+        val sharedIntKey = ValueKey("sharedInt")
+        val sharedIntValueByContent = mutableMapOf<ContentKey, Int>()
+
+        @Composable
+        fun SceneScope.animateContentInt(targetValue: Int) {
+            val animatedValue = animateContentIntAsState(targetValue, sharedIntKey)
+            LaunchedEffect(animatedValue) {
+                try {
+                    snapshotFlow { animatedValue.value }
+                        .collect { sharedIntValueByContent[contentKey] = it }
+                } finally {
+                    sharedIntValueByContent.remove(contentKey)
+                }
+            }
+        }
+
+        rule.testReplaceOverlayTransition(
+            currentSceneContent = {
+                Box(Modifier.size(width = 180.dp, height = 120.dp)) {
+                    animateContentInt(targetValue = 1_000)
+                    Foo(width = 60.dp, height = 40.dp)
+                }
+            },
+            fromContent = {},
+            fromAlignment = Alignment.TopStart,
+            toContent = {
+                animateContentInt(targetValue = 2_000)
+                Foo(width = 100.dp, height = 80.dp)
+            },
+            transition = {
+                // 4 frames of animation
+                spec = tween(4 * 16, easing = LinearEasing)
+            },
+        ) {
+            // Foo moves from (0,0) with a size of 60x40dp to centered (in a 180x120dp Box) with a
+            // size of 100x80dp, so at (40,20).
+            //
+            // The animated Int goes from 1_000 to 2_000.
+            before {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertSizeIsEqualTo(60.dp, 40.dp)
+                    .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+                rule.onNode(isElement(TestElements.Foo, content = OverlayB)).assertDoesNotExist()
+
+                assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_000)
+                assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+                assertThat(sharedIntValueByContent).doesNotContainKey(OverlayB)
+            }
+
+            at(16) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(70.dp, 50.dp)
+                    .assertPositionInRootIsEqualTo(10.dp, 5.dp)
+
+                assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_250)
+                assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+                assertThat(sharedIntValueByContent).containsEntry(OverlayB, 1_250)
+            }
+
+            at(32) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(80.dp, 60.dp)
+                    .assertPositionInRootIsEqualTo(20.dp, 10.dp)
+
+                assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_500)
+                assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+                assertThat(sharedIntValueByContent).containsEntry(OverlayB, 1_500)
+            }
+
+            at(48) {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(90.dp, 70.dp)
+                    .assertPositionInRootIsEqualTo(30.dp, 15.dp)
+
+                assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_750)
+                assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+                assertThat(sharedIntValueByContent).containsEntry(OverlayB, 1_750)
+            }
+
+            after {
+                rule
+                    .onNode(isElement(TestElements.Foo, content = SceneA))
+                    .assertExists()
+                    .assertIsNotDisplayed()
+                rule.onNode(isElement(TestElements.Foo, content = OverlayA)).assertDoesNotExist()
+                rule
+                    .onNode(isElement(TestElements.Foo, content = OverlayB))
+                    .assertSizeIsEqualTo(100.dp, 80.dp)
+                    .assertPositionInRootIsEqualTo(40.dp, 20.dp)
+
+                // Outside of transitions, the value is equal to the target value in each content.
+                assertThat(sharedIntValueByContent).containsEntry(SceneA, 1_000)
+                assertThat(sharedIntValueByContent).doesNotContainKey(OverlayA)
+                assertThat(sharedIntValueByContent).containsEntry(OverlayB, 2_000)
+            }
+        }
+    }
+
+    @Test
+    fun overscrollingOverlay_movableElementNotInOverlay() {
+        val state =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutStateImpl(
+                    SceneA,
+                    transitions {
+                        // Make OverlayA overscrollable.
+                        overscroll(OverlayA, orientation = Orientation.Horizontal) {
+                            translate(ElementKey("elementThatDoesNotExist"), x = 10.dp)
+                        }
+                    }
+                )
+            }
+
+        val key = MovableElementKey("Foo", contents = setOf(SceneA))
+        val movableElementChildTag = "movableElementChildTag"
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    scene(SceneA) {
+                        MovableElement(key, Modifier) {
+                            content { Box(Modifier.testTag(movableElementChildTag).size(100.dp)) }
+                        }
+                    }
+                    overlay(OverlayA) { /* Does not contain the element. */ }
+                }
+            }
+
+        // Overscroll on Overlay A.
+        scope.launch { state.startTransition(transition(SceneA, OverlayA, progress = { 1.5f })) }
+        rule
+            .onNode(hasTestTag(movableElementChildTag) and inContent(SceneA))
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            .assertSizeIsEqualTo(100.dp)
+            .assertIsDisplayed()
+    }
+}
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 0eaecb0..9284ffd 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
@@ -18,12 +18,20 @@
 
 import androidx.activity.BackEventCompat
 import androidx.activity.ComponentActivity
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
 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
@@ -59,7 +67,23 @@
 
     @Test
     fun testPredictiveBack() {
-        val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val transitionFrames = 2
+        val layoutState =
+            rule.runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    SceneA,
+                    transitions =
+                        transitions {
+                            from(SceneA, to = SceneB) {
+                                spec =
+                                    tween(
+                                        durationMillis = transitionFrames * 16,
+                                        easing = LinearEasing
+                                    )
+                            }
+                        }
+                )
+            }
         rule.setContent {
             SceneTransitionLayout(layoutState) {
                 scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
@@ -76,7 +100,7 @@
             dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
         }
 
-        val transition = assertThat(layoutState.transitionState).isTransition()
+        val transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasProgress(0.4f)
@@ -88,12 +112,27 @@
         assertThat(layoutState.transitionState).hasCurrentScene(SceneA)
         assertThat(layoutState.transitionState).isIdle()
 
+        rule.mainClock.autoAdvance = false
+
         // Start again and commit it.
         rule.runOnUiThread {
             dispatcher.dispatchOnBackStarted(backEvent())
             dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
             dispatcher.onBackPressed()
         }
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle()
+        val transition2 = assertThat(layoutState.transitionState).isSceneTransition()
+        // verify that transition picks up progress from preview
+        assertThat(transition2).hasProgress(0.4f, tolerance = 0.0001f)
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle()
+        // verify that transition is half way between preview-end-state (0.4f) and target-state (1f)
+        // after one frame
+        assertThat(transition2).hasProgress(0.7f, tolerance = 0.0001f)
+
+        rule.mainClock.autoAdvance = true
         rule.waitForIdle()
         assertThat(layoutState.transitionState).hasCurrentScene(SceneB)
         assertThat(layoutState.transitionState).isIdle()
@@ -124,7 +163,7 @@
             dispatcher.dispatchOnBackProgressed(backEvent(progress = 0.4f))
         }
 
-        val transition = assertThat(layoutState.transitionState).isTransition()
+        val transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasPreviewProgress(0.4f)
@@ -178,13 +217,13 @@
         val dispatcher = rule.activity.onBackPressedDispatcher
         rule.runOnUiThread { dispatcher.dispatchOnBackStarted(backEvent()) }
 
-        val predictiveTransition = assertThat(layoutState.transitionState).isTransition()
+        val predictiveTransition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(predictiveTransition).hasFromScene(SceneA)
         assertThat(predictiveTransition).hasToScene(SceneB)
 
         // Start a new transition to C.
         rule.runOnUiThread { layoutState.setTargetScene(SceneC, coroutineScope) }
-        val newTransition = assertThat(layoutState.transitionState).isTransition()
+        val newTransition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(newTransition).hasFromScene(SceneA)
         assertThat(newTransition).hasToScene(SceneC)
 
@@ -198,6 +237,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/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index c8ac580..d356c25 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -30,14 +30,18 @@
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.animation.scene.transition.link.StateLink
+import com.android.compose.animation.scene.transition.seekToScene
+import com.android.compose.test.MonotonicClockTestScope
+import com.android.compose.test.TestSceneTransition
 import com.android.compose.test.runMonotonicClockTest
+import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.cancellation.CancellationException
 import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.consumeAsFlow
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.test.runTest
 import org.junit.Rule
 import org.junit.Test
@@ -58,9 +62,12 @@
     }
 
     @Test
-    fun isTransitioningTo_transition() {
+    fun isTransitioningTo_transition() = runTest {
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-        state.startTransition(transition(from = SceneA, to = SceneB))
+        state.startTransitionImmediately(
+            animationScope = backgroundScope,
+            transition(from = SceneA, to = SceneB)
+        )
 
         assertThat(state.isTransitioning()).isTrue()
         assertThat(state.isTransitioning(from = SceneA)).isTrue()
@@ -73,17 +80,16 @@
     @Test
     fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutState(SceneA)
-        assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
+        assertThat(state.setTargetScene(SceneA, animationScope = this)).isNull()
     }
 
     @Test
     fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutState(SceneA)
-        val transition = state.setTargetScene(SceneB, coroutineScope = this)
-        assertThat(transition).isNotNull()
+        val (transition, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
         assertThat(state.transitionState).isEqualTo(transition)
 
-        transition!!.finish().join()
+        job.join()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
@@ -91,11 +97,10 @@
     fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutState(SceneA)
 
-        val transition = state.setTargetScene(SceneB, coroutineScope = this)
-        assertThat(transition).isNotNull()
-        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
+        val (_, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
+        assertThat(state.setTargetScene(SceneB, animationScope = this)).isNull()
 
-        transition!!.finish().join()
+        job.join()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
@@ -103,11 +108,10 @@
     fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutState(SceneA)
 
-        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
-        val transition = state.setTargetScene(SceneC, coroutineScope = this)
-        assertThat(transition).isNotNull()
+        assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull()
+        val (_, job) = checkNotNull(state.setTargetScene(SceneC, animationScope = this))
 
-        transition!!.finish().join()
+        job.join()
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
 
@@ -118,7 +122,7 @@
         lateinit var transition: TransitionState.Transition
         val job =
             launch(start = CoroutineStart.UNDISPATCHED) {
-                transition = state.setTargetScene(SceneB, coroutineScope = this)!!
+                transition = checkNotNull(state.setTargetScene(SceneB, animationScope = this)).first
             }
         assertThat(state.transitionState).isEqualTo(transition)
 
@@ -127,18 +131,6 @@
         assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
-    @Test
-    fun transition_finishReturnsTheSameJobWhenCalledMultipleTimes() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(SceneA)
-        val transition = state.setTargetScene(SceneB, coroutineScope = this)
-        assertThat(transition).isNotNull()
-
-        val job = transition!!.finish()
-        assertThat(transition.finish()).isSameInstanceAs(job)
-        assertThat(transition.finish()).isSameInstanceAs(job)
-        assertThat(transition.finish()).isSameInstanceAs(job)
-    }
-
     private fun setupLinkedStates(
         parentInitialScene: SceneKey = SceneC,
         childInitialScene: SceneKey = SceneA,
@@ -163,22 +155,24 @@
     }
 
     @Test
-    fun linkedTransition_startsLinkAndFinishesLinkInToState() {
+    fun linkedTransition_startsLinkAndFinishesLinkInToState() = runTest {
         val (parentState, childState) = setupLinkedStates()
 
         val childTransition = transition(SceneA, SceneB)
 
-        childState.startTransition(childTransition)
+        val job =
+            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
         assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
 
-        childState.finishTransition(childTransition)
+        childTransition.finish()
+        job.join()
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
     }
 
     @Test
-    fun linkedTransition_transitiveLink() {
+    fun linkedTransition_transitiveLink() = runTest {
         val parentParentState =
             MutableSceneTransitionLayoutState(SceneB) as MutableSceneTransitionLayoutStateImpl
         val parentLink =
@@ -204,25 +198,27 @@
 
         val childTransition = transition(SceneA, SceneB)
 
-        childState.startTransition(childTransition)
+        val job =
+            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
         assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
         assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
 
-        childState.finishTransition(childTransition)
+        childTransition.finish()
+        job.join()
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
         assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
 
     @Test
-    fun linkedTransition_linkProgressIsEqual() {
+    fun linkedTransition_linkProgressIsEqual() = runTest {
         val (parentState, childState) = setupLinkedStates()
 
         var progress = 0f
         val childTransition = transition(SceneA, SceneB, progress = { progress })
 
-        childState.startTransition(childTransition)
+        childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
         assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
 
         progress = .5f
@@ -230,28 +226,32 @@
     }
 
     @Test
-    fun linkedTransition_reverseTransitionIsNotLinked() {
+    fun linkedTransition_reverseTransitionIsNotLinked() = runTest {
         val (parentState, childState) = setupLinkedStates()
 
         val childTransition = transition(SceneB, SceneA, current = { SceneB })
 
-        childState.startTransition(childTransition)
+        val job =
+            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
         assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
 
-        childState.finishTransition(childTransition)
+        childTransition.finish()
+        job.join()
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
 
     @Test
-    fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
+    fun linkedTransition_startsLinkAndFinishesLinkInFromState() = runTest {
         val (parentState, childState) = setupLinkedStates()
 
         val childTransition = transition(SceneA, SceneB, current = { SceneA })
-        childState.startTransition(childTransition)
+        val job =
+            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
 
-        childState.finishTransition(childTransition)
+        childTransition.finish()
+        job.join()
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
@@ -260,22 +260,14 @@
     fun linkedTransition_startsLinkButLinkedStateIsTakenOver() = runTest {
         val (parentState, childState) = setupLinkedStates()
 
-        val childTransition =
-            transition(
-                SceneA,
-                SceneB,
-                onFinish = { launch { /* Do nothing. */ } },
-            )
-        val parentTransition =
-            transition(
-                SceneC,
-                SceneA,
-                onFinish = { launch { /* Do nothing. */ } },
-            )
-        childState.startTransition(childTransition)
-        parentState.startTransition(parentTransition)
+        val childTransition = transition(SceneA, SceneB)
+        val parentTransition = transition(SceneC, SceneA)
+        val job =
+            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
+        parentState.startTransitionImmediately(animationScope = backgroundScope, parentTransition)
 
-        childState.finishTransition(childTransition)
+        childTransition.finish()
+        job.join()
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(parentTransition)
     }
@@ -297,11 +289,11 @@
             )
 
         // Default transition from A to B.
-        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+        assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull()
         assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1)
 
         // Go back to A.
-        state.setTargetScene(SceneA, coroutineScope = this)
+        state.setTargetScene(SceneA, animationScope = this)
         testScheduler.advanceUntilIdle()
         assertThat(state.transitionState).isIdle()
         assertThat(state.transitionState).hasCurrentScene(SceneA)
@@ -310,7 +302,7 @@
         assertThat(
                 state.setTargetScene(
                     SceneB,
-                    coroutineScope = this,
+                    animationScope = this,
                     transitionKey = transitionkey,
                 )
             )
@@ -321,7 +313,8 @@
     @Test
     fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-        state.startTransition(
+        state.startTransitionImmediately(
+            animationScope = backgroundScope,
             transition(from = SceneA, to = SceneB, current = { SceneA }, progress = { 0.2f })
         )
         assertThat(state.isTransitioning()).isTrue()
@@ -339,7 +332,10 @@
     @Test
     fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-        state.startTransition(transition(from = SceneA, to = SceneB, progress = { 0.8f }))
+        state.startTransitionImmediately(
+            animationScope = backgroundScope,
+            transition(from = SceneA, to = SceneB, progress = { 0.8f })
+        )
         assertThat(state.isTransitioning()).isTrue()
 
         // Ignore the request if the progress is not close to 0 or 1, using the threshold.
@@ -356,18 +352,12 @@
     fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
 
-        val aToB =
-            transition(
-                from = SceneA,
-                to = SceneB,
-                progress = { 0.5f },
-                onFinish = { launch { /* do nothing */ } },
-            )
-        state.startTransition(aToB)
+        val aToB = transition(from = SceneA, to = SceneB, progress = { 0.5f })
+        state.startTransitionImmediately(animationScope = backgroundScope, aToB)
         assertThat(state.currentTransitions).containsExactly(aToB).inOrder()
 
         val bToC = transition(from = SceneB, to = SceneC, progress = { 0.8f })
-        state.startTransition(bToC)
+        state.startTransitionImmediately(animationScope = backgroundScope, bToC)
         assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
 
         // Ignore the request if the progress is not close to 0 or 1, using the threshold.
@@ -385,7 +375,8 @@
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
         var progress by mutableStateOf(0f)
         var currentScene by mutableStateOf(SceneB)
-        state.startTransition(
+        state.startTransitionImmediately(
+            animationScope = backgroundScope,
             transition(
                 from = SceneA,
                 to = SceneB,
@@ -406,47 +397,51 @@
     }
 
     @Test
-    fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
+    fun linkedTransition_fuzzyLinksAreMatchedAndStarted() = runTest {
         val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
         val childTransition = transition(SceneA, SceneB)
 
-        childState.startTransition(childTransition)
+        val job =
+            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
         assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
 
-        childState.finishTransition(childTransition)
+        childTransition.finish()
+        job.join()
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
     }
 
     @Test
-    fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() {
+    fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() = runTest {
         val (parentState, childState) =
             setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
 
         val childTransition = transition(SceneA, SceneB, current = { SceneA })
 
-        childState.startTransition(childTransition)
+        val job =
+            childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
         assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
 
-        childState.finishTransition(childTransition)
+        childTransition.finish()
+        job.join()
         assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
         assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
 
     @Test
-    fun linkedTransition_fuzzyLinksAreNotMatched() {
+    fun linkedTransition_fuzzyLinksAreNotMatched() = runTest {
         val (parentState, childState) =
             setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
         val childTransition = transition(SceneA, SceneB)
 
-        childState.startTransition(childTransition)
+        childState.startTransitionImmediately(animationScope = backgroundScope, childTransition)
         assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
         assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
     }
 
-    private fun startOverscrollableTransistionFromAtoB(
+    private fun MonotonicClockTestScope.startOverscrollableTransistionFromAtoB(
         progress: () -> Float,
         sceneTransitions: SceneTransitions,
     ): MutableSceneTransitionLayoutStateImpl {
@@ -455,7 +450,8 @@
                 SceneA,
                 sceneTransitions,
             )
-        state.startTransition(
+        state.startTransitionImmediately(
+            animationScope = backgroundScope,
             transition(
                 from = SceneA,
                 to = SceneB,
@@ -478,7 +474,7 @@
                         overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
                     }
             )
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasNoOverscrollSpec()
 
         // overscroll for SceneA is NOT defined
@@ -495,7 +491,7 @@
         // overscroll for SceneB is defined
         progress.value = 1.1f
         val overscrollSpec = assertThat(transition).hasOverscrollSpec()
-        assertThat(overscrollSpec.scene).isEqualTo(SceneB)
+        assertThat(overscrollSpec.content).isEqualTo(SceneB)
     }
 
     @Test
@@ -510,13 +506,13 @@
                     }
             )
 
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasNoOverscrollSpec()
 
         // overscroll for SceneA is defined
         progress.value = -0.1f
         val overscrollSpec = assertThat(transition).hasOverscrollSpec()
-        assertThat(overscrollSpec.scene).isEqualTo(SceneA)
+        assertThat(overscrollSpec.content).isEqualTo(SceneA)
 
         // scroll from SceneA to SceneB
         progress.value = 0.5f
@@ -539,7 +535,7 @@
                 sceneTransitions = transitions {}
             )
 
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasNoOverscrollSpec()
 
         // overscroll for SceneA is NOT defined
@@ -560,54 +556,54 @@
 
     @Test
     fun multipleTransitions() = runTest {
-        val finishingTransitions = mutableSetOf<TransitionState.Transition>()
-        fun onFinish(transition: TransitionState.Transition): Job {
-            // Instead of letting the transition finish, we put the transition in the
-            // finishingTransitions set so that we can verify that finish() is called when expected
-            // and then we call state STLState.finishTransition() ourselves.
-            finishingTransitions.add(transition)
+        val frozenTransitions = mutableSetOf<TestSceneTransition>()
+        fun onFreezeAndAnimate(transition: TestSceneTransition): () -> Unit {
+            // Instead of letting the transition finish when it is frozen, we put the transition in
+            // the frozenTransitions set so that we can verify that freezeAndAnimateToCurrentState()
+            // is called when expected and then we call finish() ourselves to finish the
+            // transitions.
+            frozenTransitions.add(transition)
 
-            return backgroundScope.launch {
-                // Try to acquire a locked mutex so that this code never completes.
-                Mutex(locked = true).withLock {}
-            }
+            return { /* do nothing */ }
         }
 
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
-        val aToB = transition(SceneA, SceneB, onFinish = ::onFinish)
-        val bToC = transition(SceneB, SceneC, onFinish = ::onFinish)
-        val cToA = transition(SceneC, SceneA, onFinish = ::onFinish)
+        val aToB = transition(SceneA, SceneB, onFreezeAndAnimate = ::onFreezeAndAnimate)
+        val bToC = transition(SceneB, SceneC, onFreezeAndAnimate = ::onFreezeAndAnimate)
+        val cToA = transition(SceneC, SceneA, onFreezeAndAnimate = ::onFreezeAndAnimate)
 
         // Starting state.
-        assertThat(finishingTransitions).isEmpty()
+        assertThat(frozenTransitions).isEmpty()
         assertThat(state.currentTransitions).isEmpty()
 
         // A => B.
-        state.startTransition(aToB)
-        assertThat(finishingTransitions).isEmpty()
+        val aToBJob = state.startTransitionImmediately(animationScope = backgroundScope, aToB)
+        assertThat(frozenTransitions).isEmpty()
         assertThat(state.finishedTransitions).isEmpty()
         assertThat(state.currentTransitions).containsExactly(aToB).inOrder()
 
-        // B => C. This should automatically call finish() on aToB.
-        state.startTransition(bToC)
-        assertThat(finishingTransitions).containsExactly(aToB)
+        // B => C. This should automatically call freezeAndAnimateToCurrentState() on aToB.
+        val bToCJob = state.startTransitionImmediately(animationScope = backgroundScope, bToC)
+        assertThat(frozenTransitions).containsExactly(aToB)
         assertThat(state.finishedTransitions).isEmpty()
         assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
 
-        // C => A. This should automatically call finish() on bToC.
-        state.startTransition(cToA)
-        assertThat(finishingTransitions).containsExactly(aToB, bToC)
+        // C => A. This should automatically call freezeAndAnimateToCurrentState() on bToC.
+        state.startTransitionImmediately(animationScope = backgroundScope, cToA)
+        assertThat(frozenTransitions).containsExactly(aToB, bToC)
         assertThat(state.finishedTransitions).isEmpty()
         assertThat(state.currentTransitions).containsExactly(aToB, bToC, cToA).inOrder()
 
         // Mark bToC as finished. The list of current transitions does not change because aToB is
         // still not marked as finished.
-        state.finishTransition(bToC)
+        bToC.finish()
+        bToCJob.join()
         assertThat(state.finishedTransitions).containsExactly(bToC)
         assertThat(state.currentTransitions).containsExactly(aToB, bToC, cToA).inOrder()
 
         // Mark aToB as finished. This will remove both aToB and bToC from the list of transitions.
-        state.finishTransition(aToB)
+        aToB.finish()
+        aToBJob.join()
         assertThat(state.finishedTransitions).isEmpty()
         assertThat(state.currentTransitions).containsExactly(cToA).inOrder()
     }
@@ -617,8 +613,9 @@
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
 
         fun startTransition() {
-            val transition = transition(SceneA, SceneB, onFinish = { launch { /* do nothing */ } })
-            state.startTransition(transition)
+            val transition =
+                transition(SceneA, SceneB, onFreezeAndAnimate = { launch { /* do nothing */ } })
+            state.startTransitionImmediately(animationScope = backgroundScope, transition)
         }
 
         var hasLoggedWtf = false
@@ -641,8 +638,8 @@
         val state = MutableSceneTransitionLayoutState(SceneA)
 
         // Transition to B.
-        state.setTargetScene(SceneB, coroutineScope = this)
-        val transition = assertThat(state.transitionState).isTransition()
+        state.setTargetScene(SceneB, animationScope = this)
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasCurrentScene(SceneB)
 
         // Snap to C.
@@ -650,4 +647,92 @@
         assertThat(state.transitionState).isIdle()
         assertThat(state.transitionState).hasCurrentScene(SceneC)
     }
+
+    @Test
+    fun snapToScene_freezesCurrentTransition() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA)
+
+        // Start a transition that is never finished. We don't use backgroundScope on purpose so
+        // that this test would fail if the transition was not frozen when snapping.
+        state.startTransitionImmediately(animationScope = this, transition(SceneA, SceneB))
+        val transition = assertThat(state.transitionState).isSceneTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+
+        // Snap to C.
+        state.snapToScene(SceneC)
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneC)
+    }
+
+    @Test
+    fun seekToScene() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        val progress = Channel<Float>()
+
+        val job =
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                state.seekToScene(SceneB, progress.consumeAsFlow())
+            }
+
+        val transition = assertThat(state.transitionState).isSceneTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        assertThat(transition).hasProgress(0f)
+
+        // Change progress.
+        progress.send(0.4f)
+        assertThat(transition).hasProgress(0.4f)
+
+        // Close the channel normally to confirm the transition.
+        progress.close()
+        job.join()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneB)
+    }
+
+    @Test
+    fun seekToScene_cancelled() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        val progress = Channel<Float>()
+
+        val job =
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                state.seekToScene(SceneB, progress.consumeAsFlow())
+            }
+
+        val transition = assertThat(state.transitionState).isSceneTransition()
+        assertThat(transition).hasFromScene(SceneA)
+        assertThat(transition).hasToScene(SceneB)
+        assertThat(transition).hasProgress(0f)
+
+        // Change progress.
+        progress.send(0.4f)
+        assertThat(transition).hasProgress(0.4f)
+
+        // Close the channel with a CancellationException to cancel the transition.
+        progress.close(CancellationException())
+        job.join()
+        assertThat(state.transitionState).isIdle()
+        assertThat(state.transitionState).hasCurrentScene(SceneA)
+    }
+
+    @Test
+    fun seekToScene_interrupted() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        val progress = Channel<Float>()
+
+        val job =
+            launch(start = CoroutineStart.UNDISPATCHED) {
+                state.seekToScene(SceneB, progress.consumeAsFlow())
+            }
+
+        assertThat(state.transitionState).isSceneTransition()
+
+        // Start a new transition, interrupting the seek transition.
+        state.setTargetScene(SceneB, animationScope = this)
+
+        // The previous job is cancelled and does not infinitely collect the progress.
+        job.join()
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 84bcc28f..63ab04f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -55,10 +55,13 @@
 import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.assertSizeIsEqualTo
+import com.android.compose.test.setContentAndCreateMainScope
 import com.android.compose.test.subjects.DpOffsetSubject
 import com.android.compose.test.subjects.assertThat
+import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
@@ -179,7 +182,7 @@
 
         // Change the current scene.
         currentScene = SceneB
-        val transition = assertThat(layoutState.transitionState).isTransition()
+        val transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasProgress(0f)
@@ -241,7 +244,7 @@
         // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
         // use a linear interpolator. Foo was at (x = layoutSize - 50dp, y = 0) in SceneA and is
         // going to (x = 0, y = 0), so the offset should now be half what it was.
-        var transition = assertThat(layoutState.transitionState).isTransition()
+        var transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(0.5f)
         sharedFoo.assertWidthIsEqualTo(75.dp)
         sharedFoo.assertHeightIsEqualTo(75.dp)
@@ -269,7 +272,7 @@
         val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
 
         sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneC))
-        transition = assertThat(layoutState.transitionState).isTransition()
+        transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(interpolatedProgress)
         sharedFoo.assertWidthIsEqualTo(expectedSize)
         sharedFoo.assertHeightIsEqualTo(expectedSize)
@@ -327,17 +330,18 @@
             }
 
         val layoutTag = "layout"
-        rule.setContent {
-            SceneTransitionLayout(state, Modifier.testTag(layoutTag)) {
-                scene(SceneA) { Box(Modifier.size(50.dp)) }
-                scene(SceneB) { Box(Modifier.size(70.dp)) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state, Modifier.testTag(layoutTag)) {
+                    scene(SceneA) { Box(Modifier.size(50.dp)) }
+                    scene(SceneB) { Box(Modifier.size(70.dp)) }
+                }
             }
-        }
 
         // Overscroll on A at -100%: size should be interpolated given that there is no overscroll
         // defined for scene A.
         var progress by mutableStateOf(-1f)
-        rule.runOnIdle {
+        scope.launch {
             state.startTransition(transition(from = SceneA, to = SceneB, progress = { progress }))
         }
         rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(30.dp)
@@ -399,7 +403,7 @@
         rule.mainClock.advanceTimeBy(duration / 2)
         rule.waitForIdle()
 
-        var transition = assertThat(state.transitionState).isTransition()
+        var transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(0.5f)
 
         // A and B are composed.
@@ -412,7 +416,7 @@
         rule.mainClock.advanceTimeByFrame()
         rule.waitForIdle()
 
-        transition = assertThat(state.transitionState).isTransition()
+        transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasProgress(0f)
 
         // A, B and C are composed.
@@ -500,4 +504,19 @@
         assertThat(keyInB).isEqualTo(SceneB)
         assertThat(keyInC).isEqualTo(SceneC)
     }
+
+    @Test
+    fun overlaysMapIsNotAllocatedWhenNoOverlayIsDefined() {
+        lateinit var layoutImpl: SceneTransitionLayoutImpl
+        rule.setContent {
+            SceneTransitionLayoutForTesting(
+                remember { MutableSceneTransitionLayoutState(SceneA) },
+                onLayoutImpl = { layoutImpl = it },
+            ) {
+                scene(SceneA) { Box(Modifier.fillMaxSize()) }
+            }
+        }
+
+        assertThat(layoutImpl.overlaysOrNullForTest()).isNull()
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 04a9380..e48cd817 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -155,7 +155,7 @@
 
         // We should be at a progress = 55dp / LayoutWidth given that we use the layout size in
         // the gesture axis as swipe distance.
-        var transition = assertThat(layoutState.transitionState).isTransition()
+        var transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasCurrentScene(SceneA)
@@ -165,7 +165,7 @@
         // Release the finger. We should now be animating back to A (currentScene = SceneA) given
         // that 55dp < positional threshold.
         rule.onRoot().performTouchInput { up() }
-        transition = assertThat(layoutState.transitionState).isTransition()
+        transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasCurrentScene(SceneA)
@@ -185,7 +185,7 @@
         }
 
         // Drag is in progress, so currentScene = SceneA and progress = 56dp / LayoutHeight
-        transition = assertThat(layoutState.transitionState).isTransition()
+        transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(TestScenes.SceneC)
         assertThat(transition).hasCurrentScene(SceneA)
@@ -195,7 +195,7 @@
         // Release the finger. We should now be animating to C (currentScene = SceneC) given
         // that 56dp >= positional threshold.
         rule.onRoot().performTouchInput { up() }
-        transition = assertThat(layoutState.transitionState).isTransition()
+        transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(TestScenes.SceneC)
         assertThat(transition).hasCurrentScene(TestScenes.SceneC)
@@ -236,7 +236,7 @@
 
         // We should be animating back to A (currentScene = SceneA) given that 124 dp/s < velocity
         // threshold.
-        var transition = assertThat(layoutState.transitionState).isTransition()
+        var transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasCurrentScene(SceneA)
@@ -260,7 +260,7 @@
         }
 
         // We should be animating to C (currentScene = SceneC).
-        transition = assertThat(layoutState.transitionState).isTransition()
+        transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(TestScenes.SceneC)
         assertThat(transition).hasCurrentScene(TestScenes.SceneC)
@@ -297,7 +297,7 @@
         }
 
         // We are transitioning to B because we used 2 fingers.
-        val transition = assertThat(layoutState.transitionState).isTransition()
+        val transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(TestScenes.SceneC)
         assertThat(transition).hasToScene(SceneB)
 
@@ -332,7 +332,7 @@
         }
 
         // We are transitioning to B (and not A) because we started from the top edge.
-        var transition = assertThat(layoutState.transitionState).isTransition()
+        var transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(TestScenes.SceneC)
         assertThat(transition).hasToScene(SceneB)
 
@@ -350,7 +350,7 @@
         }
 
         // We are transitioning to B (and not A) because we started from the left edge.
-        transition = assertThat(layoutState.transitionState).isTransition()
+        transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(TestScenes.SceneC)
         assertThat(transition).hasToScene(SceneB)
 
@@ -406,7 +406,7 @@
         }
 
         // We should be at 50%
-        val transition = assertThat(layoutState.transitionState).isTransition()
+        val transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).isNotNull()
         assertThat(transition).hasProgress(0.5f)
     }
@@ -427,7 +427,7 @@
         }
 
         // We should still correctly compute that we are swiping down to scene C.
-        var transition = assertThat(layoutState.transitionState).isTransition()
+        var transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasToScene(TestScenes.SceneC)
 
         // Release the finger, animating back to scene A.
@@ -443,7 +443,7 @@
         }
 
         // We should still correctly compute that we are swiping up to scene B.
-        transition = assertThat(layoutState.transitionState).isTransition()
+        transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasToScene(SceneB)
 
         // Release the finger, animating back to scene A.
@@ -459,7 +459,7 @@
         }
 
         // We should still correctly compute that we are swiping down to scene B.
-        transition = assertThat(layoutState.transitionState).isTransition()
+        transition = assertThat(layoutState.transitionState).isSceneTransition()
         assertThat(transition).hasToScene(SceneB)
     }
 
@@ -597,7 +597,7 @@
         }
 
         rule.waitForIdle()
-        val transition = assertThat(state.transitionState).isTransition()
+        val transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         assertThat(transition).hasProgress(0.5f, tolerance = 0.01f)
@@ -614,6 +614,7 @@
                         from(SceneA, to = SceneB) { distance = FixedDistance(swipeDistance) }
 
                         overscroll(SceneB, Orientation.Vertical) {
+                            progressConverter = ProgressConverter.linear()
                             translate(TestElements.Foo, x = { 20.dp.toPx() }, y = { 30.dp.toPx() })
                         }
                     }
@@ -686,7 +687,7 @@
         }
 
         // Scene B should come from the right (end) edge.
-        var transition = assertThat(state.transitionState).isTransition()
+        var transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         rule
@@ -707,7 +708,7 @@
         }
 
         // Scene C should come from the left (start) edge.
-        transition = assertThat(state.transitionState).isTransition()
+        transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneC)
         rule
@@ -761,7 +762,7 @@
         }
 
         // Scene C should come from the right (start) edge.
-        var transition = assertThat(state.transitionState).isTransition()
+        var transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneC)
         rule
@@ -782,7 +783,7 @@
         }
 
         // Scene C should come from the left (end) edge.
-        transition = assertThat(state.transitionState).isTransition()
+        transition = assertThat(state.transitionState).isSceneTransition()
         assertThat(transition).hasFromScene(SceneA)
         assertThat(transition).hasToScene(SceneB)
         rule
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
deleted file mode 100644
index e4e4108..0000000
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt
+++ /dev/null
@@ -1,107 +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.compose.animation.scene
-
-import androidx.compose.foundation.gestures.Orientation
-import com.android.compose.animation.scene.content.state.ContentState
-import com.android.compose.animation.scene.content.state.TransitionState
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.test.TestScope
-
-/** A utility to easily create a [TransitionState.Transition] in tests. */
-fun transition(
-    from: SceneKey,
-    to: SceneKey,
-    current: () -> SceneKey = { to },
-    progress: () -> Float = { 0f },
-    progressVelocity: () -> Float = { 0f },
-    previewProgress: () -> Float = { 0f },
-    previewProgressVelocity: () -> Float = { 0f },
-    isInPreviewStage: () -> Boolean = { false },
-    interruptionProgress: () -> Float = { 0f },
-    isInitiatedByUserInput: Boolean = false,
-    isUserInputOngoing: Boolean = false,
-    isUpOrLeft: Boolean = false,
-    bouncingContent: ContentKey? = null,
-    orientation: Orientation = Orientation.Horizontal,
-    onFinish: ((TransitionState.Transition) -> Job)? = null,
-    replacedTransition: TransitionState.Transition? = null,
-): TransitionState.Transition {
-    return object :
-        TransitionState.Transition(from, to, replacedTransition),
-        ContentState.HasOverscrollProperties {
-        override val currentScene: SceneKey
-            get() = current()
-
-        override val progress: Float
-            get() = progress()
-
-        override val progressVelocity: Float
-            get() = progressVelocity()
-
-        override val previewProgress: Float
-            get() = previewProgress()
-
-        override val previewProgressVelocity: Float
-            get() = previewProgressVelocity()
-
-        override val isInPreviewStage: Boolean
-            get() = isInPreviewStage()
-
-        override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
-        override val isUserInputOngoing: Boolean = isUserInputOngoing
-        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 fun finish(): Job {
-            val onFinish =
-                onFinish
-                    ?: error(
-                        "onFinish() must be provided if finish() is called on test transitions"
-                    )
-
-            return onFinish(this)
-        }
-
-        override fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float {
-            return interruptionProgress()
-        }
-    }
-}
-
-/**
- * Return a onFinish lambda that can be used with [transition] so that the transition never
- * finishes. This allows to keep the transition in the current transitions list.
- */
-fun TestScope.neverFinish(): (TransitionState.Transition) -> Job {
-    return {
-        backgroundScope.launch {
-            // Try to acquire a locked mutex so that this code never completes.
-            Mutex(locked = true).withLock {}
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index bed6cef..f8068e6 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -232,6 +232,47 @@
     }
 
     @Test
+    fun defaultPredictiveBack() {
+        val transitions = transitions {
+            from(
+                TestScenes.SceneA,
+                to = TestScenes.SceneB,
+                preview = { fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } }
+            ) {
+                spec = tween(500)
+                fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
+                timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
+            }
+        }
+
+        // Verify that fetching the transitionSpec with the PredictiveBack key defaults to the above
+        // transition despite it not having the PredictiveBack key set.
+        val transitionSpec =
+            transitions.transitionSpec(
+                from = TestScenes.SceneA,
+                to = TestScenes.SceneB,
+                key = TransitionKey.PredictiveBack
+            )
+
+        val transformations = transitionSpec.transformationSpec().transformations
+
+        assertThat(transformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 0.1f, end = 0.8f),
+                TransformationRange(start = 100 / 500f, end = 300 / 500f),
+            )
+
+        val previewTransformations = transitionSpec.previewTransformationSpec()?.transformations
+
+        assertThat(previewTransformations)
+            .comparingElementsUsing(TRANSFORMATION_RANGE)
+            .containsExactly(
+                TransformationRange(start = 0.1f, end = 0.8f),
+            )
+    }
+
+    @Test
     fun springSpec() {
         val defaultSpec = spring<Float>(stiffness = 1f)
         val specFromAToC = spring<Float>(stiffness = 2f)
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 a12ab78..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
@@ -16,9 +16,10 @@
 
 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.ContentState
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.google.common.truth.Fact.simpleFact
 import com.google.common.truth.FailureMetadata
@@ -31,9 +32,25 @@
     return Truth.assertAbout(TransitionStateSubject.transitionStates()).that(state)
 }
 
-/** Assert on a [TransitionState.Transition]. */
-fun assertThat(transitions: TransitionState.Transition): TransitionSubject {
-    return Truth.assertAbout(TransitionSubject.transitions()).that(transitions)
+/** 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,12 +74,32 @@
         return actual as TransitionState.Idle
     }
 
-    fun isTransition(): TransitionState.Transition {
-        if (actual !is TransitionState.Transition) {
-            failWithActual(simpleFact("expected to be TransitionState.Transition"))
+    fun isSceneTransition(): TransitionState.Transition.ChangeScene {
+        if (actual !is TransitionState.Transition.ChangeScene) {
+            failWithActual(
+                simpleFact("expected to be TransitionState.Transition.ChangeCurrentScene")
+            )
         }
 
-        return actual as TransitionState.Transition
+        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 {
@@ -68,21 +109,16 @@
     }
 }
 
-class TransitionSubject
-private constructor(
+abstract class BaseTransitionSubject<T : TransitionState.Transition>(
     metadata: FailureMetadata,
-    private val actual: TransitionState.Transition,
+    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) {
@@ -132,19 +168,77 @@
         check("currentOverscrollSpec").that(actual.currentOverscrollSpec).isNull()
     }
 
-    fun hasBouncingScene(scene: SceneKey) {
-        if (actual !is ContentState.HasOverscrollProperties) {
+    fun hasBouncingContent(content: ContentKey) {
+        val actual = actual
+        if (actual !is TransitionState.HasOverscrollProperties) {
             failWithActual(simpleFact("expected to be ContentState.HasOverscrollProperties"))
         }
 
         check("bouncingContent")
-            .that((actual as ContentState.HasOverscrollProperties).bouncingContent)
-            .isEqualTo(scene)
+            .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 ->
-            TransitionSubject(metadata, actual)
-        }
+        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/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
index 46075c3..5bfc947 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt
@@ -27,7 +27,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.TestElements
 import com.android.compose.animation.scene.testTransition
-import com.android.compose.animation.scene.transition
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SetContentAndCreateScope.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SetContentAndCreateScope.kt
new file mode 100644
index 0000000..28a864f
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SetContentAndCreateScope.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.compose.test
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+
+/**
+ * Set [content] as this rule's content and return a [CoroutineScope] bound to [Dispatchers.Main]
+ * and scoped to this rule.
+ */
+fun ComposeContentTestRule.setContentAndCreateMainScope(
+    content: @Composable () -> Unit,
+): CoroutineScope {
+    lateinit var coroutineScope: CoroutineScope
+    setContent {
+        coroutineScope = rememberCoroutineScope(getContext = { Dispatchers.Main })
+        content()
+    }
+    return coroutineScope
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestOverlayTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestOverlayTransition.kt
new file mode 100644
index 0000000..646cff8
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestOverlayTransition.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.compose.test
+
+import androidx.compose.foundation.gestures.Orientation
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.content.state.TransitionState.Transition
+import kotlinx.coroutines.CompletableDeferred
+
+/** A [Transition.ShowOrHideOverlay] for tests that will be finished once [finish] is called. */
+abstract class TestOverlayTransition(
+    fromScene: SceneKey,
+    overlay: OverlayKey,
+    replacedTransition: Transition?,
+) :
+    Transition.ShowOrHideOverlay(
+        overlay = overlay,
+        fromOrToScene = fromScene,
+        fromContent = fromScene,
+        toContent = overlay,
+        replacedTransition = replacedTransition,
+    ) {
+    private val finishCompletable = CompletableDeferred<Unit>()
+
+    override suspend fun run() {
+        finishCompletable.await()
+    }
+
+    /** Finish this transition. */
+    fun finish() {
+        finishCompletable.complete(Unit)
+    }
+}
+
+/** A utility to easily create a [TestOverlayTransition] in tests. */
+fun transition(
+    fromScene: SceneKey,
+    overlay: OverlayKey,
+    isEffectivelyShown: () -> Boolean = { true },
+    progress: () -> Float = { 0f },
+    progressVelocity: () -> Float = { 0f },
+    previewProgress: () -> Float = { 0f },
+    previewProgressVelocity: () -> Float = { 0f },
+    isInPreviewStage: () -> Boolean = { false },
+    interruptionProgress: () -> Float = { 0f },
+    isInitiatedByUserInput: Boolean = false,
+    isUserInputOngoing: Boolean = false,
+    isUpOrLeft: Boolean = false,
+    bouncingContent: ContentKey? = null,
+    orientation: Orientation = Orientation.Horizontal,
+    onFreezeAndAnimate: ((TestOverlayTransition) -> Unit)? = null,
+    replacedTransition: Transition? = null,
+): TestOverlayTransition {
+    return object :
+        TestOverlayTransition(fromScene, overlay, replacedTransition),
+        TransitionState.HasOverscrollProperties {
+        override val isEffectivelyShown: Boolean
+            get() = isEffectivelyShown()
+
+        override val progress: Float
+            get() = progress()
+
+        override val progressVelocity: Float
+            get() = progressVelocity()
+
+        override val previewProgress: Float
+            get() = previewProgress()
+
+        override val previewProgressVelocity: Float
+            get() = previewProgressVelocity()
+
+        override val isInPreviewStage: Boolean
+            get() = isInPreviewStage()
+
+        override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
+        override val isUserInputOngoing: Boolean = isUserInputOngoing
+        override val isUpOrLeft: Boolean = isUpOrLeft
+        override val bouncingContent: ContentKey? = bouncingContent
+        override val orientation: Orientation = orientation
+        override val absoluteDistance = 0f
+
+        override fun freezeAndAnimateToCurrentState() {
+            if (onFreezeAndAnimate != null) {
+                onFreezeAndAnimate(this)
+            } else {
+                finish()
+            }
+        }
+
+        override fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float {
+            return interruptionProgress()
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestSceneTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestSceneTransition.kt
new file mode 100644
index 0000000..d24b895
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/TestSceneTransition.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.test
+
+import androidx.compose.foundation.gestures.Orientation
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.content.state.TransitionState.Transition
+import kotlinx.coroutines.CompletableDeferred
+
+/** A [Transition.ChangeScene] for tests that will be finished once [finish] is called. */
+abstract class TestSceneTransition(
+    fromScene: SceneKey,
+    toScene: SceneKey,
+    replacedTransition: Transition?,
+) : Transition.ChangeScene(fromScene, toScene, replacedTransition) {
+    private val finishCompletable = CompletableDeferred<Unit>()
+
+    override suspend fun run() {
+        finishCompletable.await()
+    }
+
+    /** Finish this transition. */
+    fun finish() {
+        finishCompletable.complete(Unit)
+    }
+}
+
+/** A utility to easily create a [TestSceneTransition] in tests. */
+fun transition(
+    from: SceneKey,
+    to: SceneKey,
+    current: () -> SceneKey = { to },
+    progress: () -> Float = { 0f },
+    progressVelocity: () -> Float = { 0f },
+    previewProgress: () -> Float = { 0f },
+    previewProgressVelocity: () -> Float = { 0f },
+    isInPreviewStage: () -> Boolean = { false },
+    interruptionProgress: () -> Float = { 0f },
+    isInitiatedByUserInput: Boolean = false,
+    isUserInputOngoing: Boolean = false,
+    isUpOrLeft: Boolean = false,
+    bouncingContent: ContentKey? = null,
+    orientation: Orientation = Orientation.Horizontal,
+    onFreezeAndAnimate: ((TestSceneTransition) -> Unit)? = null,
+    replacedTransition: Transition? = null,
+): TestSceneTransition {
+    return object :
+        TestSceneTransition(from, to, replacedTransition), TransitionState.HasOverscrollProperties {
+        override val currentScene: SceneKey
+            get() = current()
+
+        override val progress: Float
+            get() = progress()
+
+        override val progressVelocity: Float
+            get() = progressVelocity()
+
+        override val previewProgress: Float
+            get() = previewProgress()
+
+        override val previewProgressVelocity: Float
+            get() = previewProgressVelocity()
+
+        override val isInPreviewStage: Boolean
+            get() = isInPreviewStage()
+
+        override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
+        override val isUserInputOngoing: Boolean = isUserInputOngoing
+        override val isUpOrLeft: Boolean = isUpOrLeft
+        override val bouncingContent: ContentKey? = bouncingContent
+        override val orientation: Orientation = orientation
+        override val absoluteDistance = 0f
+
+        override fun freezeAndAnimateToCurrentState() {
+            if (onFreezeAndAnimate != null) {
+                onFreezeAndAnimate(this)
+            } else {
+                finish()
+            }
+        }
+
+        override fun interruptionProgress(layoutImpl: SceneTransitionLayoutImpl): Float {
+            return interruptionProgress()
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index 00adefb..5cccfb1 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -26,9 +26,9 @@
 @Composable
 fun TestContentScope(
     modifier: Modifier = Modifier,
+    currentScene: SceneKey = remember { SceneKey("current") },
     content: @Composable ContentScope.() -> Unit,
 ) {
-    val currentScene = remember { SceneKey("current") }
     val state = remember { MutableSceneTransitionLayoutState(currentScene) }
     SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
 }
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
index 6d063a0..22450d3 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
@@ -20,11 +20,16 @@
 import androidx.compose.ui.test.hasAnyAncestor
 import androidx.compose.ui.test.hasTestTag
 
-/** A [SemanticsMatcher] that matches [element], optionally restricted to scene [scene]. */
-fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher {
-    return if (scene == null) {
+/** A [SemanticsMatcher] that matches [element], optionally restricted to content [content]. */
+fun isElement(element: ElementKey, content: ContentKey? = null): SemanticsMatcher {
+    return if (content == null) {
         hasTestTag(element.testTag)
     } else {
-        hasTestTag(element.testTag) and hasAnyAncestor(hasTestTag(scene.testTag))
+        hasTestTag(element.testTag) and inContent(content)
     }
 }
+
+/** A [SemanticsMatcher] that matches anything inside [content]. */
+fun inContent(content: ContentKey): SemanticsMatcher {
+    return hasAnyAncestor(hasTestTag(content.testTag))
+}
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 7f26b98..25f9564 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
@@ -16,9 +16,12 @@
 
 package com.android.compose.animation.scene
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.test.SemanticsNodeInteraction
@@ -115,6 +118,97 @@
     )
 }
 
+/** Test the transition when showing [overlay] from [fromScene]. */
+fun ComposeContentTestRule.testShowOverlayTransition(
+    fromSceneContent: @Composable ContentScope.() -> Unit,
+    overlayContent: @Composable ContentScope.() -> Unit,
+    transition: TransitionBuilder.() -> Unit,
+    fromScene: SceneKey = TestScenes.SceneA,
+    overlay: OverlayKey = TestOverlays.OverlayA,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    testTransition(
+        state =
+            runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    fromScene,
+                    transitions = transitions { from(fromScene, overlay, builder = transition) },
+                )
+            },
+        transitionLayout = { state ->
+            SceneTransitionLayout(state) {
+                scene(fromScene) { fromSceneContent() }
+                overlay(overlay) { overlayContent() }
+            }
+        },
+        changeState = { state -> state.showOverlay(overlay, animationScope = this) },
+        builder = builder,
+    )
+}
+
+/** Test the transition when hiding [overlay] to [toScene]. */
+fun ComposeContentTestRule.testHideOverlayTransition(
+    toSceneContent: @Composable ContentScope.() -> Unit,
+    overlayContent: @Composable ContentScope.() -> Unit,
+    transition: TransitionBuilder.() -> Unit,
+    toScene: SceneKey = TestScenes.SceneA,
+    overlay: OverlayKey = TestOverlays.OverlayA,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    testTransition(
+        state =
+            runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    toScene,
+                    initialOverlays = setOf(overlay),
+                    transitions = transitions { from(overlay, toScene, builder = transition) },
+                )
+            },
+        transitionLayout = { state ->
+            SceneTransitionLayout(state) {
+                scene(toScene) { toSceneContent() }
+                overlay(overlay) { overlayContent() }
+            }
+        },
+        changeState = { state -> state.hideOverlay(overlay, animationScope = this) },
+        builder = builder,
+    )
+}
+
+/** Test the transition when replace [from] to [to]. */
+fun ComposeContentTestRule.testReplaceOverlayTransition(
+    fromContent: @Composable ContentScope.() -> Unit,
+    toContent: @Composable ContentScope.() -> Unit,
+    transition: TransitionBuilder.() -> Unit,
+    currentSceneContent: @Composable ContentScope.() -> Unit = { Box(Modifier.fillMaxSize()) },
+    fromAlignment: Alignment = Alignment.Center,
+    toAlignment: Alignment = Alignment.Center,
+    from: OverlayKey = TestOverlays.OverlayA,
+    to: OverlayKey = TestOverlays.OverlayB,
+    currentScene: SceneKey = TestScenes.SceneA,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
+    testTransition(
+        state =
+            runOnUiThread {
+                MutableSceneTransitionLayoutState(
+                    currentScene,
+                    initialOverlays = setOf(from),
+                    transitions = transitions { from(from, to, builder = transition) },
+                )
+            },
+        transitionLayout = { state ->
+            SceneTransitionLayout(state) {
+                scene(currentScene) { currentSceneContent() }
+                overlay(from, alignment = fromAlignment) { fromContent() }
+                overlay(to, alignment = toAlignment) { toContent() }
+            }
+        },
+        changeState = { state -> state.replaceOverlay(from, to, animationScope = this) },
+        builder = builder,
+    )
+}
+
 data class TransitionRecordingSpec(
     val recordBefore: Boolean = true,
     val recordAfter: Boolean = true,
@@ -152,7 +246,7 @@
         content = { play ->
             LaunchedEffect(play) {
                 if (play) {
-                    state.setTargetScene(toScene, coroutineScope = this)
+                    state.setTargetScene(toScene, animationScope = this)
                 }
             }
 
@@ -188,6 +282,21 @@
             "(${currentScene.debugName})"
     }
 
+    testTransition(
+        state = state,
+        changeState = { state -> state.setTargetScene(to, animationScope = this) },
+        transitionLayout = transitionLayout,
+        builder = builder,
+    )
+}
+
+/** Test the transition from [state] to [to]. */
+fun ComposeContentTestRule.testTransition(
+    state: MutableSceneTransitionLayoutState,
+    changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
+    transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
+    builder: TransitionTestBuilder.() -> Unit,
+) {
     val test = transitionTest(builder)
     val assertionScope =
         object : TransitionTestAssertionScope {
@@ -213,7 +322,7 @@
     mainClock.autoAdvance = false
 
     // Change the current scene.
-    runOnUiThread { state.setTargetScene(to, coroutineScope) }
+    runOnUiThread { coroutineScope.changeState(state) }
     waitForIdle()
     mainClock.advanceTimeByFrame()
     waitForIdle()
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
index b83705a..f39dd67 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
@@ -21,7 +21,7 @@
 import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
 
-/** Scenes keys that can be reused by tests. */
+/** Scene keys that can be reused by tests. */
 object TestScenes {
     val SceneA = SceneKey("SceneA")
     val SceneB = SceneKey("SceneB")
@@ -29,6 +29,12 @@
     val SceneD = SceneKey("SceneD")
 }
 
+/** Overlay keys that can be reused by tests. */
+object TestOverlays {
+    val OverlayA = OverlayKey("OverlayA")
+    val OverlayB = OverlayKey("OverlayB")
+}
+
 /** Element keys that can be reused by tests. */
 object TestElements {
     val Foo = ElementKey("Foo")
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/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 362e23d..96d79df 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -71,7 +71,7 @@
             .stateIn(
                 scope = backgroundScope,
                 started = SharingStarted.Eagerly,
-                initialValue = false,
+                initialValue = true,
             )
 
     /** The default duration for DND mode when enabled. See [Settings.Secure.ZEN_DURATION]. */
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
index 2f50bbd..0ac15c5 100644
--- a/packages/SystemUI/docs/scene.md
+++ b/packages/SystemUI/docs/scene.md
@@ -121,11 +121,11 @@
 do so by defining their own scene. This section describes how to do that.
 
 Each scene is defined as an implementation of the
-[`ComposableScene`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt)
+[`Scene`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/Scene.kt)
 interface, which has three parts: 1. The `key` property returns the
 [`SceneKey`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt)
-that uniquely identifies that scene 2. The `destinationScenes` `Flow` returns
-the (potentially ever-changing) set of navigation edges to other scenes, based
+that uniquely identifies that scene 2. The `userActions` `Flow` returns
+the (potentially ever-changing) set of navigation edges to other content, based
 on user-actions, which is how the navigation graph is defined (see
 [the Scene navigation](#Scene-navigation) section for more) 3. The `Content`
 function which uses
@@ -138,10 +138,10 @@
 For example:
 
 ```kotlin
-@SysUISingleton class YourScene @Inject constructor( /* your dependencies here */ ) : ComposableScene {
+@SysUISingleton class YourScene @Inject constructor( /* your dependencies here */ ) : Scene {
     override val key = SceneKey.YourScene
 
-    override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
+    override val userActions: StateFlow<Map<UserAction, SceneModel>> =
         MutableStateFlow<Map<UserAction, SceneModel>>(
             mapOf(
                 // This is where scene navigation is defined, more on that below.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 7707a60..062d351 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -29,6 +29,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
+import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
@@ -56,7 +57,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import com.android.systemui.Flags as AconfigFlags
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -66,8 +66,7 @@
 class KeyguardPasswordViewControllerTest : SysuiTestCase() {
     @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
     @Mock private lateinit var passwordEntry: EditText
-    private var passwordEntryLayoutParams =
-        ViewGroup.LayoutParams(/* width = */ 0, /* height = */ 0)
+    private var passwordEntryLayoutParams = ViewGroup.LayoutParams(/* width= */ 0, /* height= */ 0)
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
     @Mock lateinit var lockPatternUtils: LockPatternUtils
@@ -85,6 +84,7 @@
     private lateinit var mKeyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
     @Mock private lateinit var postureController: DevicePostureController
+    @Mock private lateinit var mUserActivityNotifier: UserActivityNotifier
     @Captor private lateinit var keyListenerArgumentCaptor: ArgumentCaptor<View.OnKeyListener>
 
     private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
@@ -106,6 +106,8 @@
         whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
             .thenReturn(mock(ImageView::class.java))
         `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+        // TODO(b/362362385): No need to mock keyguardPasswordView.context once this bug is fixed.
+        `when`(keyguardPasswordView.context).thenReturn(context)
         whenever(passwordEntry.layoutParams).thenReturn(passwordEntryLayoutParams)
         val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
         val fakeFeatureFlags = FakeFeatureFlags()
@@ -130,6 +132,8 @@
                 fakeFeatureFlags,
                 mSelectedUserInteractor,
                 keyguardKeyboardInteractor,
+                null,
+                mUserActivityNotifier
             )
     }
 
@@ -187,9 +191,11 @@
         verify(passwordEntry).setOnKeyListener(keyListenerArgumentCaptor.capture())
 
         val eventHandled =
-            keyListenerArgumentCaptor.value.onKey(keyguardPasswordView,
+            keyListenerArgumentCaptor.value.onKey(
+                keyguardPasswordView,
                 KeyEvent.KEYCODE_SPACE,
-                KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE))
+                KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)
+            )
 
         assertFalse("Unlock attempted.", eventHandled)
     }
@@ -204,9 +210,11 @@
         verify(passwordEntry).setOnKeyListener(keyListenerArgumentCaptor.capture())
 
         val eventHandled =
-            keyListenerArgumentCaptor.value.onKey(keyguardPasswordView,
+            keyListenerArgumentCaptor.value.onKey(
+                keyguardPasswordView,
                 KeyEvent.KEYCODE_ENTER,
-                KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER))
+                KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER)
+            )
 
         assertTrue("Unlock not attempted.", eventHandled)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 0054d13..1076d90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -87,6 +87,8 @@
     private View mOkButton;
     @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
+    private UserActivityNotifier mUserActivityNotifier;
     private NumPadKey[] mButtons = new NumPadKey[]{};
 
     private KeyguardPinBasedInputViewController mKeyguardPinViewController;
@@ -117,7 +119,7 @@
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
                 mEmergencyButtonController, mFalsingCollector, featureFlags,
-                mSelectedUserInteractor, keyguardKeyboardInteractor) {
+                mSelectedUserInteractor, keyguardKeyboardInteractor, null, mUserActivityNotifier) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index f7f69d3..e444db4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -18,10 +18,8 @@
 package com.android.keyguard
 
 import android.app.admin.DevicePolicyManager
-import android.app.admin.flags.Flags as DevicePolicyFlags
 import android.content.res.Configuration
 import android.media.AudioManager
-import android.platform.test.annotations.EnableFlags
 import android.telephony.TelephonyManager
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.TestableResources
@@ -56,6 +54,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
@@ -153,6 +152,7 @@
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var postureController: DevicePostureController
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var mUserActivityNotifier: UserActivityNotifier
 
     @Captor
     private lateinit var swipeListenerArgumentCaptor:
@@ -238,6 +238,8 @@
                 featureFlags,
                 mSelectedUserInteractor,
                 keyguardKeyboardInteractor,
+                null,
+                mUserActivityNotifier
             )
 
         kosmos = testKosmos()
@@ -279,7 +281,7 @@
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
                 devicePolicyManager,
-                keyguardTransitionInteractor,
+                kosmos.keyguardDismissTransitionInteractor,
                 { primaryBouncerInteractor },
             ) {
                 deviceEntryInteractor
@@ -938,7 +940,6 @@
     }
 
     @Test
-    @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
     fun showAlmostAtWipeDialog_calledOnMainUser_setsCorrectUserType() {
         val mainUserId = 10
 
@@ -955,7 +956,6 @@
     }
 
     @Test
-    @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
     fun showAlmostAtWipeDialog_calledOnNonMainUser_setsCorrectUserType() {
         val secondaryUserId = 10
         val mainUserId = 0
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/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
index 460461a..176c3ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -22,10 +22,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
@@ -38,13 +39,15 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val secureSettings = kosmos.fakeSettings
+
     @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
     // mocks
     @Mock private lateinit var a11yManager: AccessibilityManager
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
-    private val secureSettings = FakeSettings()
 
     private val userA11yQsShortcutsRepositoryFactory =
         object : UserA11yQsShortcutsRepository.Factory {
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/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index 4e1f82c..801d359 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -23,11 +23,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -41,9 +42,10 @@
 
     private val testUser1 = UserHandle.of(1)!!
     private val testUser2 = UserHandle.of(2)!!
-    private val testDispatcher = StandardTestDispatcher()
-    private val scope = TestScope(testDispatcher)
-    private val settings: FakeSettings = FakeSettings()
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val scope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
 
     private lateinit var underTest: ColorCorrectionRepository
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index b99dec4..2f457be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -23,11 +23,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -41,9 +42,10 @@
 
     private val testUser1 = UserHandle.of(1)!!
     private val testUser2 = UserHandle.of(2)!!
-    private val testDispatcher = StandardTestDispatcher()
-    private val scope = TestScope(testDispatcher)
-    private val settings: FakeSettings = FakeSettings()
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val scope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
 
     private lateinit var underTest: ColorInversionRepository
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
index 5757f67..54dbed8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt
@@ -26,7 +26,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dagger.NightDisplayListenerModule
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.android.systemui.user.utils.UserScopedService
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
@@ -38,8 +40,6 @@
 import com.google.common.truth.Truth.assertThat
 import java.time.LocalTime
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -51,7 +51,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class NightDisplayRepositoryTest : SysuiTestCase() {
-    private val kosmos = Kosmos()
+    private val kosmos = testKosmos()
     private val testUser = UserHandle.of(1)!!
     private val testStartTime = LocalTime.MIDNIGHT
     private val testEndTime = LocalTime.NOON
@@ -71,8 +71,8 @@
         }
     private val globalSettings = kosmos.fakeGlobalSettings
     private val secureSettings = kosmos.fakeSettings
-    private val testDispatcher = StandardTestDispatcher()
-    private val scope = TestScope(testDispatcher)
+    private val testDispatcher = kosmos.testDispatcher
+    private val scope = kosmos.testScope
     private val userScopedColorDisplayManager =
         mock<UserScopedService<ColorDisplayManager>> {
             whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
index 1378dac..729d356 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt
@@ -22,11 +22,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -39,9 +40,10 @@
 
     private val testUser1 = UserHandle.of(1)!!
     private val testUser2 = UserHandle.of(2)!!
-    private val testDispatcher = StandardTestDispatcher()
-    private val scope = TestScope(testDispatcher)
-    private val settings: FakeSettings = FakeSettings()
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val scope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
 
     private val underTest: OneHandedModeRepository =
         OneHandedModeRepositoryImpl(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
index ce22e28..62f13f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -21,10 +21,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -32,9 +33,10 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
-    private val secureSettings = FakeSettings()
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val secureSettings = kosmos.fakeSettings
 
     private val underTest =
         UserA11yQsShortcutsRepository(
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 98%
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
index d7acaaf..80de087 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -33,7 +33,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.DismissView;
 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import org.junit.Before;
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 99%
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
index 4373c88..46f076a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -46,7 +46,7 @@
 import com.android.systemui.accessibility.MotionEventHelper;
 import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.DismissView;
 
 import org.junit.After;
 import org.junit.Before;
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/statusbar/ui/AmbientStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
index 201ed00..43db5a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
@@ -148,6 +148,7 @@
                 mKosmos.getWifiInteractor(),
                 mKosmos.getCommunalSceneInteractor(),
                 mLogBuffer);
+        mController.onInit();
     }
 
     @Test
@@ -517,6 +518,15 @@
         verify(mDreamOverlayStateController).setDreamOverlayStatusBarVisible(false);
     }
 
+    @Test
+    public void testStatusBarWindowStateControllerListenerLifecycle() {
+        ArgumentCaptor<StatusBarWindowStateListener> listenerCaptor =
+                ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+        verify(mStatusBarWindowStateController).addListener(listenerCaptor.capture());
+        mController.destroy();
+        verify(mStatusBarWindowStateController).removeListener(eq(listenerCaptor.getValue()));
+    }
+
     private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
         when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
         final ArgumentCaptor<StatusBarWindowStateListener>
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/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 1cd9d76..72163e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -31,9 +31,7 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.whenever
@@ -58,14 +56,13 @@
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
     @Mock private lateinit var getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>
-    @Mock private lateinit var tableLogger: TableLogBuffer
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val clock = FakeSystemClock()
     private val userRepository = FakeUserRepository()
-    private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
+    private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
 
     private lateinit var underTest: AuthenticationRepository
 
@@ -78,8 +75,6 @@
         userRepository.setUserInfos(USER_INFOS)
         runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
         whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode }
-        mobileConnectionsRepository =
-            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
 
         underTest =
             AuthenticationRepositoryImpl(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 0c5e726..080b48a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.authentication.domain.interactor
 
 import android.app.admin.DevicePolicyManager
-import android.app.admin.flags.Flags as DevicePolicyFlags
-import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
@@ -414,7 +412,6 @@
         }
 
     @Test
-    @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES)
     fun upcomingWipe() =
         testScope.runTest {
             val upcomingWipe by collectLastValue(underTest.upcomingWipe)
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/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 752c93e..b7d99d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -79,6 +79,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.app.viewcapture.ViewCapture;
 import com.android.internal.R;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
@@ -96,6 +97,8 @@
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -165,6 +168,8 @@
     private PromptViewModel mPromptViewModel;
     @Mock
     private UdfpsUtils mUdfpsUtils;
+    @Mock
+    private Lazy<ViewCapture> mLazyViewCapture;
 
     @Captor
     private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
@@ -1060,7 +1065,8 @@
                     mWakefulnessLifecycle, mUserManager, mLockPatternUtils, () -> mUdfpsLogger,
                     () -> mLogContextInteractor, () -> mPromptSelectionInteractor,
                     () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
-                    mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper);
+                    mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper,
+                    mLazyViewCapture);
         }
 
         @Override
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/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index cfe0bec..65c9b72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.bouncer.domain.interactor
 
 import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricFaceConstants
+import android.hardware.biometrics.BiometricSourceType
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -25,6 +27,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FaceSensorInfo
@@ -36,7 +39,6 @@
 import com.android.systemui.bouncer.shared.model.BouncerMessageModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
 import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
@@ -58,7 +60,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -77,6 +81,8 @@
     @Mock private lateinit var securityModel: KeyguardSecurityModel
     @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
     @Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
+    @Captor
+    private lateinit var keyguardUpdateMonitorCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
 
     private lateinit var underTest: BouncerMessageInteractor
 
@@ -107,8 +113,6 @@
                 systemPropertiesHelper = systemPropertiesHelper,
                 primaryBouncerInteractor = kosmos.primaryBouncerInteractor,
                 facePropertyRepository = kosmos.fakeFacePropertyRepository,
-                deviceEntryFingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor,
-                faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository,
                 securityModel = securityModel,
                 deviceEntryBiometricsAllowedInteractor =
                     kosmos.deviceEntryBiometricsAllowedInteractor,
@@ -207,6 +211,52 @@
         }
 
     @Test
+    fun resetMessageBackToDefault_faceAuthRestarts() =
+        testScope.runTest {
+            init()
+            captureKeyguardUpdateMonitorCallback()
+            val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+            underTest.setFaceAcquisitionMessage("not empty")
+
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+
+            keyguardUpdateMonitorCaptor.value.onBiometricAcquired(
+                BiometricSourceType.FACE,
+                BiometricFaceConstants.FACE_ACQUIRED_START
+            )
+
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(bouncerMessage?.secondaryMessage?.message).isNull()
+        }
+
+    @Test
+    fun faceRestartDoesNotResetFingerprintMessage() =
+        testScope.runTest {
+            init()
+            captureKeyguardUpdateMonitorCallback()
+            val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+            underTest.setFingerprintAcquisitionMessage("not empty")
+
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+
+            keyguardUpdateMonitorCaptor.value.onBiometricAcquired(
+                BiometricSourceType.FACE,
+                BiometricFaceConstants.FACE_ACQUIRED_START
+            )
+
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(bouncerMessage?.secondaryMessage?.message).isEqualTo("not empty")
+        }
+
+    @Test
     fun setFingerprintMessage_propagateValue() =
         testScope.runTest {
             init()
@@ -284,6 +334,32 @@
         }
 
     @Test
+    fun faceLockoutThenFaceFailure_doesNotUpdateMessage() =
+        testScope.runTest {
+            init()
+            captureKeyguardUpdateMonitorCallback()
+            val bouncerMessage by collectLastValue(underTest.bouncerMessage)
+
+            kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+            kosmos.fakeDeviceEntryFaceAuthRepository.setLockedOut(true)
+            runCurrent()
+
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(secondaryResMessage(bouncerMessage))
+                .isEqualTo("Can’t unlock with face. Too many attempts.")
+
+            // WHEN face failure comes in during lockout
+            keyguardUpdateMonitorCaptor.value.onBiometricAuthFailed(BiometricSourceType.FACE)
+
+            // THEN lockout message does NOT update to face failure message
+            assertThat(primaryResMessage(bouncerMessage))
+                .isEqualTo("Unlock with PIN or fingerprint")
+            assertThat(secondaryResMessage(bouncerMessage))
+                .isEqualTo("Can’t unlock with face. Too many attempts.")
+        }
+
+    @Test
     fun onFaceLockoutStateChange_whenFaceIsNotEnrolled_isANoop() =
         testScope.runTest {
             init()
@@ -576,6 +652,10 @@
         }
     }
 
+    private fun captureKeyguardUpdateMonitorCallback() {
+        verify(updateMonitor).registerCallback(keyguardUpdateMonitorCaptor.capture())
+    }
+
     companion object {
         private const val PRIMARY_USER_ID = 0
         private val PRIMARY_USER =
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/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index c161525..8c8faee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -19,9 +19,11 @@
 import android.content.pm.UserInfo
 import android.hardware.biometrics.BiometricFaceConstants
 import android.hardware.fingerprint.FingerprintManager
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -35,7 +37,6 @@
 import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.shared.flag.fakeComposeBouncerFlags
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
@@ -71,6 +72,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@EnableFlags(Flags.FLAG_COMPOSE_BOUNCER)
 class BouncerMessageViewModelTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
@@ -82,7 +84,6 @@
     @Before
     fun setUp() {
         kosmos.fakeUserRepository.setUserInfos(listOf(PRIMARY_USER))
-        kosmos.fakeComposeBouncerFlags.composeBouncerEnabled = true
         overrideResource(
             R.array.config_face_acquire_device_entry_ignorelist,
             intArrayOf(ignoreHelpMessageId)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt
similarity index 93%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt
index a86a0c0..f58bbc3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModelTest.kt
@@ -44,17 +44,17 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @EnableSceneContainer
-class BouncerSceneActionsViewModelTest : SysuiTestCase() {
+class BouncerUserActionsViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    private lateinit var underTest: BouncerSceneActionsViewModel
+    private lateinit var underTest: BouncerUserActionsViewModel
 
     @Before
     fun setUp() {
         kosmos.sceneContainerStartable.start()
-        underTest = kosmos.bouncerSceneActionsViewModel
+        underTest = kosmos.bouncerUserActionsViewModel
         underTest.activateIn(testScope)
     }
 
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 96%
rename from packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index ec8cc4d..53d82d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -34,8 +34,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -83,7 +81,6 @@
     private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
     private final FalsingClassifier.Result mFalsedResult =
             FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
-    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     @Before
     public void setup() {
@@ -101,8 +98,7 @@
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
                 mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
                 mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
-                mAccessibilityManager, false, mFakeFeatureFlags);
-        mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
+                mAccessibilityManager, false);
     }
 
     @Test
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 99%
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
index 983a435..edc8c83 100644
--- 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
@@ -69,19 +69,19 @@
                 FakeWidgetMetadata(
                     widgetId = 11,
                     componentName = "com.android.fakePackage1/fakeWidget1",
-                    rank = 3,
+                    rank = 0,
                     userSerialNumber = 0,
                 ),
                 FakeWidgetMetadata(
                     widgetId = 12,
                     componentName = "com.android.fakePackage2/fakeWidget2",
-                    rank = 2,
+                    rank = 1,
                     userSerialNumber = 0,
                 ),
                 FakeWidgetMetadata(
                     widgetId = 13,
                     componentName = "com.android.fakePackage3/fakeWidget3",
-                    rank = 1,
+                    rank = 2,
                     userSerialNumber = 10,
                 ),
             )
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 78%
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
index d670508..d4d966a 100644
--- 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
@@ -67,11 +67,11 @@
     @Test
     fun addWidget_readValueInDb() =
         testScope.runTest {
-            val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
+            val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
             communalWidgetDao.addWidget(
                 widgetId = widgetId,
                 provider = provider,
-                priority = priority,
+                rank = rank,
                 userSerialNumber = userSerialNumber,
             )
             val entry = communalWidgetDao.getWidgetByIdNow(id = 1)
@@ -81,11 +81,11 @@
     @Test
     fun deleteWidget_notInDb_returnsFalse() =
         testScope.runTest {
-            val (widgetId, provider, priority, userSerialNumber) = widgetInfo1
+            val (widgetId, provider, rank, userSerialNumber) = widgetInfo1
             communalWidgetDao.addWidget(
                 widgetId = widgetId,
                 provider = provider,
-                priority = priority,
+                rank = rank,
                 userSerialNumber = userSerialNumber,
             )
             assertThat(communalWidgetDao.deleteWidgetById(widgetId = 123)).isFalse()
@@ -97,11 +97,11 @@
             val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
             widgetsToAdd.forEach {
-                val (widgetId, provider, priority, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userSerialNumber
                 )
             }
@@ -115,17 +115,48 @@
         }
 
     @Test
+    fun addWidget_rankNotSpecified_widgetAddedAtTheEnd(): Unit =
+        testScope.runTest {
+            val widgets by collectLastValue(communalWidgetDao.getWidgets())
+
+            // Verify database is empty
+            assertThat(widgets).isEmpty()
+
+            // Add widgets one by one without specifying rank
+            val widgetsToAdd = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
+            widgetsToAdd.forEach {
+                val (widgetId, provider, _, userSerialNumber) = it
+                communalWidgetDao.addWidget(
+                    widgetId = widgetId,
+                    provider = provider,
+                    userSerialNumber = userSerialNumber
+                )
+            }
+
+            // Verify new each widget is added at the end
+            assertThat(widgets)
+                .containsExactly(
+                    communalItemRankEntry1,
+                    communalWidgetItemEntry1,
+                    communalItemRankEntry2,
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry3,
+                    communalWidgetItemEntry3,
+                )
+        }
+
+    @Test
     fun deleteWidget_emitsActiveWidgetsInDb() =
         testScope.runTest {
             val widgetsToAdd = listOf(widgetInfo1, widgetInfo2)
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             widgetsToAdd.forEach {
-                val (widgetId, provider, priority, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userSerialNumber,
                 )
             }
@@ -148,32 +179,32 @@
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             widgetsToAdd.forEach {
-                val (widgetId, provider, priority, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userSerialNumber,
                 )
             }
             assertThat(widgets())
                 .containsExactly(
-                    communalItemRankEntry2,
-                    communalWidgetItemEntry2,
                     communalItemRankEntry1,
                     communalWidgetItemEntry1,
+                    communalItemRankEntry2,
+                    communalWidgetItemEntry2,
                 )
                 .inOrder()
 
-            // swapped priorities
-            val widgetIdsToPriorityMap = mapOf(widgetInfo1.widgetId to 2, widgetInfo2.widgetId to 1)
-            communalWidgetDao.updateWidgetOrder(widgetIdsToPriorityMap)
+            // swapped ranks
+            val widgetIdsToRankMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 0)
+            communalWidgetDao.updateWidgetOrder(widgetIdsToRankMap)
             assertThat(widgets())
                 .containsExactly(
-                    communalItemRankEntry1.copy(rank = 2),
+                    communalItemRankEntry2.copy(rank = 0),
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry1.copy(rank = 1),
                     communalWidgetItemEntry1,
-                    communalItemRankEntry2.copy(rank = 1),
-                    communalWidgetItemEntry2
                 )
                 .inOrder()
         }
@@ -181,53 +212,56 @@
     @Test
     fun addNewWidgetWithReorder_emitsWidgetsInNewOrder() =
         testScope.runTest {
-            val existingWidgets = listOf(widgetInfo1, widgetInfo2)
+            val existingWidgets = listOf(widgetInfo1, widgetInfo2, widgetInfo3)
             val widgets = collectLastValue(communalWidgetDao.getWidgets())
 
             existingWidgets.forEach {
-                val (widgetId, provider, priority, userSerialNumber) = it
+                val (widgetId, provider, rank, userSerialNumber) = it
                 communalWidgetDao.addWidget(
                     widgetId = widgetId,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userSerialNumber,
                 )
             }
             assertThat(widgets())
                 .containsExactly(
-                    communalItemRankEntry2,
-                    communalWidgetItemEntry2,
                     communalItemRankEntry1,
                     communalWidgetItemEntry1,
+                    communalItemRankEntry2,
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry3,
+                    communalWidgetItemEntry3,
                 )
                 .inOrder()
 
-            // map with no item in the middle at index 1
-            val widgetIdsToIndexMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 3)
-            communalWidgetDao.updateWidgetOrder(widgetIdsToIndexMap)
-            assertThat(widgets())
-                .containsExactly(
-                    communalItemRankEntry2.copy(rank = 3),
-                    communalWidgetItemEntry2,
-                    communalItemRankEntry1.copy(rank = 1),
-                    communalWidgetItemEntry1,
-                )
-                .inOrder()
-            // add the new middle item that we left space for.
+            // add a new widget at rank 1.
             communalWidgetDao.addWidget(
-                widgetId = widgetInfo3.widgetId,
-                provider = widgetInfo3.provider,
-                priority = 2,
-                userSerialNumber = widgetInfo3.userSerialNumber,
+                widgetId = 4,
+                provider = ComponentName("pk_name", "cls_name_4"),
+                rank = 1,
+                userSerialNumber = 0,
             )
+
+            val newRankEntry = CommunalItemRank(uid = 4L, rank = 1)
+            val newWidgetEntry =
+                CommunalWidgetItem(
+                    uid = 4L,
+                    widgetId = 4,
+                    componentName = "pk_name/cls_name_4",
+                    itemId = 4L,
+                    userSerialNumber = 0,
+                )
             assertThat(widgets())
                 .containsExactly(
-                    communalItemRankEntry2.copy(rank = 3),
-                    communalWidgetItemEntry2,
-                    communalItemRankEntry3.copy(rank = 2),
-                    communalWidgetItemEntry3,
-                    communalItemRankEntry1.copy(rank = 1),
+                    communalItemRankEntry1.copy(rank = 0),
                     communalWidgetItemEntry1,
+                    newRankEntry,
+                    newWidgetEntry,
+                    communalItemRankEntry2.copy(rank = 2),
+                    communalWidgetItemEntry2,
+                    communalItemRankEntry3.copy(rank = 3),
+                    communalWidgetItemEntry3,
                 )
                 .inOrder()
         }
@@ -261,11 +295,11 @@
             assertThat(widgets).containsExactlyEntriesIn(expected)
         }
 
-    private fun addWidget(metadata: FakeWidgetMetadata, priority: Int? = null) {
+    private fun addWidget(metadata: FakeWidgetMetadata, rank: Int? = null) {
         communalWidgetDao.addWidget(
             widgetId = metadata.widgetId,
             provider = metadata.provider,
-            priority = priority ?: metadata.priority,
+            rank = rank ?: metadata.rank,
             userSerialNumber = metadata.userSerialNumber,
         )
     }
@@ -273,7 +307,7 @@
     data class FakeWidgetMetadata(
         val widgetId: Int,
         val provider: ComponentName,
-        val priority: Int,
+        val rank: Int,
         val userSerialNumber: Int,
     )
 
@@ -282,26 +316,26 @@
             FakeWidgetMetadata(
                 widgetId = 1,
                 provider = ComponentName("pk_name", "cls_name_1"),
-                priority = 1,
+                rank = 0,
                 userSerialNumber = 0,
             )
         val widgetInfo2 =
             FakeWidgetMetadata(
                 widgetId = 2,
                 provider = ComponentName("pk_name", "cls_name_2"),
-                priority = 2,
+                rank = 1,
                 userSerialNumber = 0,
             )
         val widgetInfo3 =
             FakeWidgetMetadata(
                 widgetId = 3,
                 provider = ComponentName("pk_name", "cls_name_3"),
-                priority = 3,
+                rank = 2,
                 userSerialNumber = 10,
             )
-        val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
-        val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
-        val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.priority)
+        val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.rank)
+        val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.rank)
+        val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.rank)
         val communalWidgetItemEntry1 =
             CommunalWidgetItem(
                 uid = 1L,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
index ad2c42f..eba395b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -115,21 +115,21 @@
                 .addWidget(
                     widgetId = 0,
                     componentName = defaultWidgets[0],
-                    priority = 3,
+                    rank = 0,
                     userSerialNumber = 0,
                 )
             verify(communalWidgetDao)
                 .addWidget(
                     widgetId = 1,
                     componentName = defaultWidgets[1],
-                    priority = 2,
+                    rank = 1,
                     userSerialNumber = 0,
                 )
             verify(communalWidgetDao)
                 .addWidget(
                     widgetId = 2,
                     componentName = defaultWidgets[2],
-                    priority = 1,
+                    rank = 2,
                     userSerialNumber = 0,
                 )
         }
@@ -150,7 +150,7 @@
                 .addWidget(
                     widgetId = anyInt(),
                     componentName = any(),
-                    priority = anyInt(),
+                    rank = anyInt(),
                     userSerialNumber = anyInt(),
                 )
         }
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/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
index c37b33e..ae1c496 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -45,8 +45,7 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-
-    private lateinit var secureSettings: FakeSettings
+    private val secureSettings = kosmos.fakeSettings
     private lateinit var userRepository: FakeUserRepository
 
     private lateinit var underTest: CommunalTutorialRepositoryImpl
@@ -55,7 +54,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        secureSettings = FakeSettings()
         userRepository = FakeUserRepository()
         val listOfUserInfo = listOf(MAIN_USER_INFO)
         userRepository.setUserInfos(listOfUserInfo)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index ca81838..980a5ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -154,7 +154,7 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = communalWidgetItemEntry.widgetId,
                         providerInfo = providerInfoA,
-                        priority = communalItemRankEntry.rank,
+                        rank = communalItemRankEntry.rank,
                     )
                 )
 
@@ -190,12 +190,12 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
-                        priority = 1,
+                        rank = 1,
                     ),
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 2,
                         providerInfo = providerInfoB,
-                        priority = 2,
+                        rank = 2,
                     ),
                 )
         }
@@ -225,12 +225,12 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
-                        priority = 1,
+                        rank = 1,
                     ),
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 2,
                         providerInfo = providerInfoB,
-                        priority = 2,
+                        rank = 2,
                     ),
                 )
 
@@ -248,12 +248,12 @@
                         appWidgetId = 1,
                         // Verify that provider info updated
                         providerInfo = providerInfoC,
-                        priority = 1,
+                        rank = 1,
                     ),
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 2,
                         providerInfo = providerInfoB,
-                        priority = 2,
+                        rank = 2,
                     ),
                 )
         }
@@ -263,7 +263,7 @@
         testScope.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
-            val priority = 1
+            val rank = 1
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(
@@ -273,12 +273,11 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorSuccess)
+            underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorSuccess)
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
-            verify(communalWidgetDao)
-                .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
+            verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -289,7 +288,7 @@
         testScope.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
-            val priority = 1
+            val rank = 1
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(
@@ -299,7 +298,7 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
+            underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorFail)
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
@@ -316,7 +315,7 @@
         testScope.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
-            val priority = 1
+            val rank = 1
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
             whenever(
@@ -326,7 +325,7 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, mainUser, priority) {
+            underTest.addWidget(provider, mainUser, rank) {
                 throw IllegalStateException("some error")
             }
             runCurrent()
@@ -345,7 +344,7 @@
         testScope.runTest {
             val provider = ComponentName("pkg_name", "cls_name")
             val id = 1
-            val priority = 1
+            val rank = 1
             whenever(communalWidgetHost.getAppWidgetInfo(id))
                 .thenReturn(PROVIDER_INFO_CONFIGURATION_OPTIONAL)
             whenever(
@@ -355,12 +354,11 @@
                     )
                 )
                 .thenReturn(id)
-            underTest.addWidget(provider, mainUser, priority, kosmos.widgetConfiguratorFail)
+            underTest.addWidget(provider, mainUser, rank, kosmos.widgetConfiguratorFail)
             runCurrent()
 
             verify(communalWidgetHost).allocateIdAndBindWidget(provider, mainUser)
-            verify(communalWidgetDao)
-                .addWidget(id, provider, priority, testUserSerialNumber(mainUser))
+            verify(communalWidgetDao).addWidget(id, provider, rank, testUserSerialNumber(mainUser))
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -399,11 +397,11 @@
     @Test
     fun reorderWidgets_queryDb() =
         testScope.runTest {
-            val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
-            underTest.updateWidgetOrder(widgetIdToPriorityMap)
+            val widgetIdToRankMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+            underTest.updateWidgetOrder(widgetIdToRankMap)
             runCurrent()
 
-            verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
+            verify(communalWidgetDao).updateWidgetOrder(widgetIdToRankMap)
 
             // Verify backup requested
             verify(backupManager).dataChanged()
@@ -691,11 +689,11 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
-                        priority = 1,
+                        rank = 1,
                     ),
                     CommunalWidgetContentModel.Pending(
                         appWidgetId = 2,
-                        priority = 2,
+                        rank = 2,
                         componentName = ComponentName("pk_2", "cls_2"),
                         icon = fakeIcon,
                         user = mainUser,
@@ -730,7 +728,7 @@
                 .containsExactly(
                     CommunalWidgetContentModel.Pending(
                         appWidgetId = 1,
-                        priority = 1,
+                        rank = 1,
                         componentName = ComponentName("pk_1", "cls_1"),
                         icon = fakeIcon,
                         user = mainUser,
@@ -750,7 +748,7 @@
                     CommunalWidgetContentModel.Available(
                         appWidgetId = 1,
                         providerInfo = providerInfoA,
-                        priority = 1,
+                        rank = 1,
                     ),
                 )
         }
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..777ddab 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
@@ -68,15 +68,16 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.statusbar.phone.fakeManagedProfileController
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeManagedProfileController
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -122,6 +123,7 @@
     private lateinit var userTracker: FakeUserTracker
     private lateinit var activityStarter: ActivityStarter
     private lateinit var userManager: UserManager
+    private lateinit var managedProfileController: FakeManagedProfileController
 
     private lateinit var underTest: CommunalInteractor
 
@@ -143,6 +145,7 @@
         userTracker = kosmos.fakeUserTracker
         activityStarter = kosmos.activityStarter
         userManager = kosmos.userManager
+        managedProfileController = kosmos.fakeManagedProfileController
 
         whenever(mainUser.isMain).thenReturn(true)
         whenever(secondaryUser.isMain).thenReturn(false)
@@ -352,7 +355,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 +371,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 +379,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 +401,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 +780,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 +1063,24 @@
             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)
+        }
+    }
+
+    @Test
+    fun unpauseWorkProfileEnablesWorkMode() =
+        testScope.runTest {
+            underTest.unpauseWorkProfile()
+
+            assertThat(managedProfileController.isWorkModeEnabled()).isTrue()
+        }
+
     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/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index c5518b0..2ba4bf9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 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
@@ -834,4 +835,86 @@
                     )
                 )
         }
+
+    /**
+     * KTF: LOCKSCREEN -> ALTERNATE_BOUNCER starts but then STL: GLANCEABLE_HUB -> BLANK interrupts.
+     *
+     * Verifies that we correctly cancel the previous KTF state before starting the glanceable hub
+     * transition.
+     */
+    @Test
+    fun transition_to_blank_after_ktf_started_another_transition() =
+        testScope.runTest {
+            // Another transition has already started to the alternate bouncer.
+            keyguardTransitionRepository.startTransition(
+                TransitionInfo(
+                    from = LOCKSCREEN,
+                    to = ALTERNATE_BOUNCER,
+                    animator = null,
+                    ownerName = "external",
+                    modeOnCanceled = TransitionModeOnCanceled.RESET
+                ),
+            )
+
+            val allSteps by collectValues(keyguardTransitionRepository.transitions)
+            // Keep track of existing size to drop any pre-existing steps that we don't
+            // care about.
+            val numToDrop = allSteps.size
+
+            sceneTransitions.value = hubToBlank
+            runCurrent()
+            progress.emit(0.4f)
+            runCurrent()
+            // We land on blank.
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+            // We should cancel the previous ALTERNATE_BOUNCER transition and transition back
+            // to the GLANCEABLE_HUB before we can transition away from it.
+            assertThat(allSteps.drop(numToDrop))
+                .containsExactly(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = ALTERNATE_BOUNCER,
+                        transitionState = CANCELED,
+                        value = 0f,
+                        ownerName = "external",
+                    ),
+                    TransitionStep(
+                        from = ALTERNATE_BOUNCER,
+                        to = GLANCEABLE_HUB,
+                        transitionState = STARTED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = ALTERNATE_BOUNCER,
+                        to = GLANCEABLE_HUB,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = STARTED,
+                        value = 0f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = RUNNING,
+                        value = 0.4f,
+                        ownerName = ownerName,
+                    ),
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = LOCKSCREEN,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    ),
+                )
+                .inOrder()
+        }
 }
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 57ce9de..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,
             )
     }
 
@@ -150,8 +148,8 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
             // Widgets available.
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             // Smartspace available.
             smartspaceRepository.setTimers(
@@ -212,8 +210,8 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
             // Widgets available.
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             val communalContent by collectLastValue(underTest.communalContent)
 
@@ -227,7 +225,7 @@
             underTest.onDeleteWidget(
                 id = 0,
                 componentName = ComponentName("test_package", "test_class"),
-                priority = 30,
+                rank = 30,
             )
 
             // Only one widget and CTA tile remain.
@@ -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 cc945d6..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,11 +97,11 @@
 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
 import org.mockito.kotlin.spy
-import org.mockito.kotlin.times
 import org.mockito.kotlin.whenever
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
@@ -160,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
@@ -213,8 +216,8 @@
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
             // Widgets available.
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             // Smartspace available.
             smartspaceRepository.setTimers(
@@ -303,7 +306,7 @@
         testScope.runTest {
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
-            widgetRepository.addWidget(appWidgetId = 1, priority = 1)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 1)
             mediaRepository.mediaInactive()
             smartspaceRepository.setTimers(emptyList())
 
@@ -660,8 +663,8 @@
             )
 
             // Widgets available
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             // Then hub shows widgets and the CTA tile
             assertThat(communalContent).hasSize(3)
@@ -716,8 +719,8 @@
             )
 
             // And widgets available
-            widgetRepository.addWidget(appWidgetId = 0, priority = 30)
-            widgetRepository.addWidget(appWidgetId = 1, priority = 20)
+            widgetRepository.addWidget(appWidgetId = 0, rank = 30)
+            widgetRepository.addWidget(appWidgetId = 1, rank = 20)
 
             // Then emits widgets and the CTA tile
             assertThat(communalContent).hasSize(3)
@@ -755,7 +758,7 @@
 
             // updateViewVisibility is called when the flow is collected.
             assertThat(communalContent).isNotNull()
-            verify(mediaHost).updateViewVisibility()
+            verify(mediaHost, atLeastOnce()).updateViewVisibility()
         }
 
     @Test
@@ -770,7 +773,7 @@
 
     @Test
     fun onTapWidget_logEvent() {
-        underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), priority = 10)
+        underTest.onTapWidget(ComponentName("test_pkg", "test_cls"), rank = 10)
         verify(metricsLogger).logTapWidget("test_pkg/test_cls", rank = 10)
     }
 
@@ -785,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/EditWidgetsActivityControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
new file mode 100644
index 0000000..3ba8625
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.communal.widgets
+
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EditWidgetsActivityControllerTest : SysuiTestCase() {
+    @Test
+    fun activityLifecycle_finishedWhenNotWaitingForResult() {
+        val activity = mock<Activity>()
+        val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        controller.setActivityFullyVisible(true)
+        callbackCapture.lastValue.onActivityStopped(activity)
+
+        verify(activity).finish()
+    }
+
+    @Test
+    fun activityLifecycle_notFinishedWhenOnStartCalledAfterOnStop() {
+        val activity = mock<Activity>()
+
+        val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        controller.setActivityFullyVisible(false)
+        callbackCapture.lastValue.onActivityStopped(activity)
+        callbackCapture.lastValue.onActivityStarted(activity)
+
+        verify(activity, never()).finish()
+    }
+
+    @Test
+    fun activityLifecycle_notFinishedDuringConfigurationChange() {
+        val activity = mock<Activity>()
+
+        val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        controller.setActivityFullyVisible(true)
+        whenever(activity.isChangingConfigurations).thenReturn(true)
+        callbackCapture.lastValue.onActivityStopped(activity)
+        callbackCapture.lastValue.onActivityStarted(activity)
+
+        verify(activity, never()).finish()
+    }
+
+    @Test
+    fun activityLifecycle_notFinishedWhenWaitingForResult() {
+        val activity = mock<Activity>()
+        val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        controller.onWaitingForResult(true)
+        callbackCapture.lastValue.onActivityStopped(activity)
+
+        verify(activity, never()).finish()
+    }
+
+    @Test
+    fun activityLifecycle_finishedAfterResultReturned() {
+        val activity = mock<Activity>()
+        val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+
+        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+        controller.onWaitingForResult(true)
+        controller.onWaitingForResult(false)
+        controller.setActivityFullyVisible(true)
+        callbackCapture.lastValue.onActivityStopped(activity)
+
+        verify(activity).finish()
+    }
+
+    @Test
+    fun activityLifecycle_statePreservedThroughInstanceSave() {
+        val activity = mock<Activity>()
+        val bundle = Bundle(1)
+
+        run {
+            val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+            val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+            verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+            controller.onWaitingForResult(true)
+            callbackCapture.lastValue.onActivitySaveInstanceState(activity, bundle)
+        }
+
+        clearInvocations(activity)
+
+        run {
+            val controller = EditWidgetsActivity.ActivityControllerImpl(activity)
+            val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
+            verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
+
+            callbackCapture.lastValue.onActivityCreated(activity, bundle)
+            callbackCapture.lastValue.onActivityStopped(activity)
+
+            verify(activity, never()).finish()
+        }
+    }
+}
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/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 91259a6..71abed7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -34,6 +34,7 @@
 import android.os.CancellationSignal
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.internal.logging.InstanceId.fakeInstanceId
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.Flags as AConfigFlags
@@ -55,6 +56,7 @@
 import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -73,10 +75,13 @@
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectionStatus
@@ -85,11 +90,12 @@
 import com.android.systemui.util.mockito.captureMany
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-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.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -136,12 +142,12 @@
 
     @Captor
     private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
-    private val testDispatcher = kosmos.testDispatcher
+    private val testDispatcher by lazy { kosmos.testDispatcher }
 
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val testScope = kosmos.testScope
-    private val fakeUserRepository = kosmos.fakeUserRepository
-    private val fakeExecutor = kosmos.fakeExecutor
+    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+    private val testScope by lazy { kosmos.testScope }
+    private val fakeUserRepository by lazy { kosmos.fakeUserRepository }
+    private val fakeExecutor by lazy { kosmos.fakeExecutor }
     private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
     private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
     private lateinit var authRunning: FlowValue<Boolean?>
@@ -149,18 +155,19 @@
     private lateinit var lockedOut: FlowValue<Boolean?>
     private lateinit var canFaceAuthRun: FlowValue<Boolean?>
     private lateinit var authenticated: FlowValue<Boolean?>
-    private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
-    private val deviceEntryFingerprintAuthRepository =
+    private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository }
+    private val deviceEntryFingerprintAuthRepository by lazy {
         kosmos.fakeDeviceEntryFingerprintAuthRepository
-    private val trustRepository = kosmos.fakeTrustRepository
-    private val keyguardRepository = kosmos.fakeKeyguardRepository
-    private val powerInteractor = kosmos.powerInteractor
-    private val keyguardInteractor = kosmos.keyguardInteractor
-    private val alternateBouncerInteractor = kosmos.alternateBouncerInteractor
-    private val displayStateInteractor = kosmos.displayStateInteractor
-    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
-    private val displayRepository = kosmos.displayRepository
-    private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
+    }
+    private val trustRepository by lazy { kosmos.fakeTrustRepository }
+    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    private val powerInteractor by lazy { kosmos.powerInteractor }
+    private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
+    private val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
+    private val displayStateInteractor by lazy { kosmos.displayStateInteractor }
+    private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+    private val displayRepository by lazy { kosmos.displayRepository }
+    private val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
     private lateinit var featureFlags: FakeFeatureFlags
 
     private var wasAuthCancelled = false
@@ -180,34 +187,19 @@
         whenever(bypassController.bypassEnabled).thenReturn(true)
         underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
 
-        mSetFlagsRule.disableFlags(
-            AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
-        )
+        if (!SceneContainerFlag.isEnabled) {
+            mSetFlagsRule.disableFlags(
+                AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+            )
+        }
     }
 
     private fun createDeviceEntryFaceAuthRepositoryImpl(
         fmOverride: FaceManager? = faceManager,
         bypassControllerOverride: KeyguardBypassController? = bypassController
     ): DeviceEntryFaceAuthRepositoryImpl {
-        val systemClock = FakeSystemClock()
-        val faceAuthBuffer =
-            TableLogBuffer(
-                10,
-                "face auth",
-                systemClock,
-                mock(),
-                testDispatcher,
-                testScope.backgroundScope
-            )
-        val faceDetectBuffer =
-            TableLogBuffer(
-                10,
-                "face detect",
-                systemClock,
-                mock(),
-                testDispatcher,
-                testScope.backgroundScope
-            )
+        val faceAuthBuffer = logcatTableLogBuffer(kosmos, "face auth")
+        val faceDetectBuffer = logcatTableLogBuffer(kosmos, "face detect")
 
         return DeviceEntryFaceAuthRepositoryImpl(
             mContext,
@@ -227,6 +219,7 @@
             powerInteractor,
             keyguardInteractor,
             alternateBouncerInteractor,
+            { kosmos.sceneInteractor },
             faceDetectBuffer,
             faceAuthBuffer,
             keyguardTransitionInteractor,
@@ -547,6 +540,39 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun withSceneContainerEnabled_authenticateDoesNotRunWhenKeyguardIsGoingAway() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth(sceneContainerEnabled = true) {
+                kosmos.sceneInteractor.setTransitionState(
+                    MutableStateFlow(
+                        ObservableTransitionState.Transition(
+                            fromScene = Scenes.Bouncer,
+                            toScene = Scenes.Gone,
+                            currentScene = flowOf(Scenes.Bouncer),
+                            progress = MutableStateFlow(0.2f),
+                            isInitiatedByUserInput = true,
+                            isUserInputOngoing = flowOf(false),
+                        )
+                    )
+                )
+                runCurrent()
+            }
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun withSceneContainerEnabled_authenticateDoesNotRunWhenLockscreenIsGone() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth(sceneContainerEnabled = true) {
+                kosmos.sceneInteractor.setTransitionState(
+                    MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
+                )
+                runCurrent()
+            }
+        }
+
+    @Test
     fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -595,6 +621,31 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun withSceneContainer_authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled = true)
+            bouncerRepository.setAlternateVisible(false)
+
+            // launch secure camera
+            keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+            keyguardRepository.setKeyguardOccluded(true)
+            kosmos.sceneInteractor.snapToScene(Scenes.Lockscreen, "for-test")
+            runCurrent()
+            assertThat(canFaceAuthRun()).isFalse()
+
+            // but bouncer is shown after that.
+            kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+            kosmos.sceneInteractor.setTransitionState(
+                MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+            )
+            runCurrent()
+
+            assertThat(canFaceAuthRun()).isTrue()
+        }
+
+    @Test
     fun authenticateDoesNotRunOnUnsupportedPosture() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -841,6 +892,41 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun withSceneContainer_faceDetectDoesNotRunWhenKeyguardGoingAway() =
+        testScope.runTest {
+            testGatingCheckForDetect(sceneContainerEnabled = true) {
+                kosmos.sceneInteractor.setTransitionState(
+                    MutableStateFlow(
+                        ObservableTransitionState.Transition(
+                            fromScene = Scenes.Bouncer,
+                            toScene = Scenes.Gone,
+                            currentScene = flowOf(Scenes.Bouncer),
+                            progress = MutableStateFlow(0.2f),
+                            isInitiatedByUserInput = true,
+                            isUserInputOngoing = flowOf(false),
+                        )
+                    )
+                )
+
+                runCurrent()
+            }
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun withSceneContainer_faceDetectDoesNotRunWhenLockscreenIsGone() =
+        testScope.runTest {
+            testGatingCheckForDetect(sceneContainerEnabled = true) {
+                kosmos.sceneInteractor.setTransitionState(
+                    MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
+                )
+
+                runCurrent()
+            }
+        }
+
+    @Test
     fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
         testScope.runTest {
             testGatingCheckForDetect {
@@ -1052,10 +1138,11 @@
         }
 
     private suspend fun TestScope.testGatingCheckForFaceAuth(
+        sceneContainerEnabled: Boolean = false,
         gatingCheckModifier: suspend () -> Unit
     ) {
         initCollectors()
-        allPreconditionsToRunFaceAuthAreTrue()
+        allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
 
         gatingCheckModifier()
         runCurrent()
@@ -1069,7 +1156,7 @@
         faceAuthenticateIsNotCalled()
 
         // flip the gating check back on.
-        allPreconditionsToRunFaceAuthAreTrue()
+        allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
         assertThat(underTest.canRunFaceAuth.value).isTrue()
 
         faceAuthenticateIsCalled()
@@ -1094,10 +1181,11 @@
     }
 
     private suspend fun TestScope.testGatingCheckForDetect(
+        sceneContainerEnabled: Boolean = false,
         gatingCheckModifier: suspend () -> Unit
     ) {
         initCollectors()
-        allPreconditionsToRunFaceAuthAreTrue()
+        allPreconditionsToRunFaceAuthAreTrue(sceneContainerEnabled)
 
         // This will stop face auth from running but is required to be false for detect.
         biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
@@ -1145,12 +1233,25 @@
         cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
     }
 
-    private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
+    private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue(
+        sceneContainerEnabled: Boolean = false
+    ) {
         fakeExecutor.runAllReady()
         verify(faceManager, atLeastOnce())
             .addLockoutResetCallback(faceLockoutResetCallback.capture())
         trustRepository.setCurrentUserTrusted(false)
-        keyguardRepository.setKeyguardGoingAway(false)
+        if (sceneContainerEnabled) {
+            // Keyguard is not going away
+            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(KeyguardState.OFF, KeyguardState.LOCKSCREEN, value = 1.0f),
+                validateStep = false
+            )
+            kosmos.sceneInteractor.setTransitionState(
+                MutableStateFlow(ObservableTransitionState.Idle(Scenes.Lockscreen))
+            )
+        } else {
+            keyguardRepository.setKeyguardGoingAway(false)
+        }
         powerInteractor.setAwakeForTest()
         biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
         biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
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/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 6412276..3895595 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -62,6 +62,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -324,4 +325,13 @@
         // enabled.
         mController.onViewAttached();
     }
+
+    @Test
+    public void destroy_cleansUpState() {
+        mController.destroy();
+        verify(mStateController).removeCallback(any());
+        verify(mAmbientStatusBarViewController).destroy();
+        verify(mComplicationHostViewController).destroy();
+        verify(mLowLightTransitionCoordinator).setLowLightEnterListener(ArgumentMatchers.isNull());
+    }
 }
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 89ec3cf..7426574 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,10 +66,11 @@
 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
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -84,12 +86,18 @@
 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)
 @SmallTest
@@ -115,8 +123,6 @@
 
     @Mock lateinit var mComplicationComponentFactory: ComplicationComponent.Factory
 
-    @Mock lateinit var mComplicationComponent: ComplicationComponent
-
     @Mock lateinit var mComplicationHostViewController: ComplicationHostViewController
 
     @Mock lateinit var mComplicationVisibilityController: ComplicationLayoutEngine
@@ -125,20 +131,12 @@
     lateinit var mDreamComplicationComponentFactory:
         com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
 
-    @Mock
-    lateinit var mDreamComplicationComponent:
-        com.android.systemui.dreams.complication.dagger.ComplicationComponent
-
     @Mock lateinit var mHideComplicationTouchHandler: HideComplicationTouchHandler
 
     @Mock lateinit var mDreamOverlayComponentFactory: DreamOverlayComponent.Factory
 
-    @Mock lateinit var mDreamOverlayComponent: DreamOverlayComponent
-
     @Mock lateinit var mAmbientTouchComponentFactory: AmbientTouchComponent.Factory
 
-    @Mock lateinit var mAmbientTouchComponent: AmbientTouchComponent
-
     @Mock lateinit var mDreamOverlayContainerView: DreamOverlayContainerView
 
     @Mock lateinit var mDreamOverlayContainerViewController: DreamOverlayContainerViewController
@@ -170,10 +168,83 @@
     private lateinit var communalRepository: FakeCommunalSceneRepository
     private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
     private lateinit var gestureInteractor: GestureInteractor
+    private lateinit var environmentComponents: EnvironmentComponents
 
     @Captor var mViewCaptor: ArgumentCaptor<View>? = null
     private lateinit var mService: DreamOverlayService
 
+    private class EnvironmentComponents(
+        val dreamsComplicationComponent:
+            com.android.systemui.dreams.complication.dagger.ComplicationComponent,
+        val dreamOverlayComponent: DreamOverlayComponent,
+        val complicationComponent: ComplicationComponent,
+        val ambientTouchComponent: AmbientTouchComponent,
+    ) {
+        fun clearInvocations() {
+            clearInvocations(
+                dreamsComplicationComponent,
+                dreamOverlayComponent,
+                complicationComponent,
+                ambientTouchComponent
+            )
+        }
+
+        fun verifyNoMoreInteractions() {
+            Mockito.verifyNoMoreInteractions(
+                dreamsComplicationComponent,
+                dreamOverlayComponent,
+                complicationComponent,
+                ambientTouchComponent
+            )
+        }
+    }
+
+    private fun setupComponentFactories(
+        dreamComplicationComponentFactory:
+            com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory,
+        dreamOverlayComponentFactory: DreamOverlayComponent.Factory,
+        complicationComponentFactory: ComplicationComponent.Factory,
+        ambientTouchComponentFactory: AmbientTouchComponent.Factory
+    ): EnvironmentComponents {
+        val dreamOverlayComponent = mock<DreamOverlayComponent>()
+        whenever(dreamOverlayComponent.getDreamOverlayContainerViewController())
+            .thenReturn(mDreamOverlayContainerViewController)
+
+        val complicationComponent = mock<ComplicationComponent>()
+        whenever(complicationComponent.getComplicationHostViewController())
+            .thenReturn(mComplicationHostViewController)
+        whenever(mLifecycleOwner.registry).thenReturn(lifecycleRegistry)
+
+        mCommunalInteractor = Mockito.spy(kosmos.communalInteractor)
+
+        whenever(complicationComponentFactory.create(any(), any(), any(), any()))
+            .thenReturn(complicationComponent)
+        whenever(complicationComponent.getVisibilityController())
+            .thenReturn(mComplicationVisibilityController)
+
+        val dreamComplicationComponent =
+            mock<com.android.systemui.dreams.complication.dagger.ComplicationComponent>()
+        whenever(dreamComplicationComponent.getHideComplicationTouchHandler())
+            .thenReturn(mHideComplicationTouchHandler)
+        whenever(dreamComplicationComponentFactory.create(any(), any()))
+            .thenReturn(dreamComplicationComponent)
+
+        whenever(dreamOverlayComponentFactory.create(any(), any(), any()))
+            .thenReturn(dreamOverlayComponent)
+
+        val ambientTouchComponent = mock<AmbientTouchComponent>()
+        whenever(ambientTouchComponentFactory.create(any(), any(), any()))
+            .thenReturn(ambientTouchComponent)
+        whenever(ambientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
+
+        return EnvironmentComponents(
+            dreamComplicationComponent,
+            dreamOverlayComponent,
+            complicationComponent,
+            ambientTouchComponent
+        )
+    }
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
@@ -183,27 +254,14 @@
         communalRepository = kosmos.fakeCommunalSceneRepository
         gestureInteractor = spy(kosmos.gestureInteractor)
 
-        whenever(mDreamOverlayComponent.getDreamOverlayContainerViewController())
-            .thenReturn(mDreamOverlayContainerViewController)
-        whenever(mComplicationComponent.getComplicationHostViewController())
-            .thenReturn(mComplicationHostViewController)
-        whenever(mLifecycleOwner.registry).thenReturn(lifecycleRegistry)
+        environmentComponents =
+            setupComponentFactories(
+                mDreamComplicationComponentFactory,
+                mDreamOverlayComponentFactory,
+                mComplicationComponentFactory,
+                mAmbientTouchComponentFactory
+            )
 
-        mCommunalInteractor = Mockito.spy(kosmos.communalInteractor)
-
-        whenever(mComplicationComponentFactory.create(any(), any(), any(), any()))
-            .thenReturn(mComplicationComponent)
-        whenever(mComplicationComponent.getVisibilityController())
-            .thenReturn(mComplicationVisibilityController)
-        whenever(mDreamComplicationComponent.getHideComplicationTouchHandler())
-            .thenReturn(mHideComplicationTouchHandler)
-        whenever(mDreamComplicationComponentFactory.create(any(), any()))
-            .thenReturn(mDreamComplicationComponent)
-        whenever(mDreamOverlayComponentFactory.create(any(), any(), any()))
-            .thenReturn(mDreamOverlayComponent)
-        whenever(mAmbientTouchComponentFactory.create(any(), any()))
-            .thenReturn(mAmbientTouchComponent)
-        whenever(mAmbientTouchComponent.getTouchMonitor()).thenReturn(mTouchMonitor)
         whenever(mDreamOverlayContainerViewController.containerView)
             .thenReturn(mDreamOverlayContainerView)
         whenever(mScrimManager.getCurrentController()).thenReturn(mScrimController)
@@ -264,6 +322,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -281,6 +340,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -300,6 +360,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -322,6 +383,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -340,6 +402,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -355,6 +418,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -362,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
 
@@ -370,6 +475,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             LOW_LIGHT_COMPONENT.flattenToString(),
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -386,6 +492,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             HOME_CONTROL_PANEL_DREAM_COMPONENT.flattenToString(),
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -402,6 +509,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             LOW_LIGHT_COMPONENT.flattenToString(),
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -436,6 +544,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         // Immediately end the dream.
@@ -467,6 +576,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -486,6 +596,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             LOW_LIGHT_COMPONENT.flattenToString(),
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -537,6 +648,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -560,6 +672,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -570,9 +683,8 @@
 
         // Assert that the overlay is not showing complications.
         assertThat(mService.shouldShowComplications()).isFalse()
-        Mockito.clearInvocations(mDreamOverlayComponent)
-        Mockito.clearInvocations(mAmbientTouchComponent)
-        Mockito.clearInvocations(mWindowManager)
+        environmentComponents.clearInvocations()
+        clearInvocations(mWindowManager)
 
         // New dream starting with dream complications showing. Note that when a new dream is
         // binding to the dream overlay service, it receives the same instance of IBinder as the
@@ -581,6 +693,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -594,8 +707,11 @@
 
         // Verify that new instances of overlay container view controller and overlay touch monitor
         // are created.
-        verify(mDreamOverlayComponent).getDreamOverlayContainerViewController()
-        verify(mAmbientTouchComponent).getTouchMonitor()
+        verify(environmentComponents.dreamOverlayComponent).getDreamOverlayContainerViewController()
+        verify(environmentComponents.ambientTouchComponent).getTouchMonitor()
+
+        // Verify DreamOverlayContainerViewController is destroyed.
+        verify(mDreamOverlayContainerViewController).destroy()
     }
 
     @Test
@@ -607,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
@@ -630,6 +747,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -655,6 +773,7 @@
                 mWindowParams,
                 mDreamOverlayCallback,
                 DREAM_COMPONENT,
+                false /*isPreview*/,
                 false /*shouldShowComplication*/
             )
             mMainExecutor.runAllReady()
@@ -680,6 +799,7 @@
                 mWindowParams,
                 mDreamOverlayCallback,
                 DREAM_COMPONENT,
+                false /*isPreview*/,
                 false /*shouldShowComplication*/
             )
             // Set communal available, verify that overlay callback is informed.
@@ -708,6 +828,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -728,6 +849,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -747,6 +869,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             true /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -770,6 +893,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -800,6 +924,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         testScope.runCurrent()
@@ -816,6 +941,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -839,6 +965,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -870,6 +997,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -901,6 +1029,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -936,6 +1065,7 @@
             mWindowParams,
             mDreamOverlayCallback,
             DREAM_COMPONENT,
+            false /*isPreview*/,
             false /*shouldShowComplication*/
         )
         mMainExecutor.runAllReady()
@@ -962,7 +1092,7 @@
     }
 
     @Test
-    fun testDreamActivityGesturesBlockedOnStart() {
+    fun testDreamActivityGesturesBlockedWhenDreaming() {
         val client = client
 
         // Inform the overlay service of dream starting.
@@ -970,36 +1100,104 @@
             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
+    fun testDreamActivityGesturesNotBlockedWhenNotificationShadeShowing() {
+        val client = client
+
+        // Inform the overlay service of dream starting.
+        client.startDream(
+            mWindowParams,
+            mDreamOverlayCallback,
+            DREAM_COMPONENT,
+            false /*isPreview*/,
+            false /*shouldShowComplication*/
+        )
+        mMainExecutor.runAllReady()
+
+        val matcherCaptor = argumentCaptor<TaskMatcher>()
+        verify(gestureInteractor)
+            .addGestureBlockedMatcher(matcherCaptor.capture(), eq(GestureInteractor.Scope.Global))
+        val matcher = matcherCaptor.firstValue
+
+        val dreamTaskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM)
+        assertThat(matcher.matches(dreamTaskInfo)).isTrue()
+
+        val callbackCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+        verify(mKeyguardUpdateMonitor).registerCallback(callbackCaptor.capture())
+
+        // Notification shade opens.
+        callbackCaptor.value.onShadeExpandedChanged(true)
+        mMainExecutor.runAllReady()
+
+        verify(gestureInteractor)
+            .removeGestureBlockedMatcher(eq(matcher), eq(GestureInteractor.Scope.Global))
+    }
+
+    @Test
+    fun testComponentsRecreatedBetweenDreams() {
+        clearInvocations(
+            mDreamComplicationComponentFactory,
+            mDreamOverlayComponentFactory,
+            mComplicationComponentFactory,
+            mAmbientTouchComponentFactory
+        )
+
+        mService.onEndDream()
+
+        setupComponentFactories(
+            mDreamComplicationComponentFactory,
+            mDreamOverlayComponentFactory,
+            mComplicationComponentFactory,
+            mAmbientTouchComponentFactory
+        )
+
+        client.startDream(
+            mWindowParams,
+            mDreamOverlayCallback,
+            DREAM_COMPONENT,
+            false /*isPreview*/,
+            false /*shouldShowComplication*/
+        )
+        mMainExecutor.runAllReady()
+        environmentComponents.verifyNoMoreInteractions()
     }
 
     internal class FakeLifecycleRegistry(provider: LifecycleOwner) : LifecycleRegistry(provider) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index f82beff..50b727c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.SysuiTestableContext
 import com.android.systemui.contextualeducation.GestureType.BACK
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
 import com.android.systemui.education.data.model.GestureEduModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
@@ -105,6 +106,19 @@
             assertThat(model).isEqualTo(newModel)
         }
 
+    @Test
+    fun eduDeviceConnectionTimeDataChangedOnUpdate() =
+        testScope.runTest {
+            val newModel =
+                EduDeviceConnectionTime(
+                    keyboardFirstConnectionTime = kosmos.fakeEduClock.instant(),
+                    touchpadFirstConnectionTime = kosmos.fakeEduClock.instant(),
+                )
+            underTest.updateEduDeviceConnectionTime { newModel }
+            val model by collectLastValue(underTest.readEduDeviceConnectionTime())
+            assertThat(model).isEqualTo(newModel)
+        }
+
     /** Test context which allows overriding getFilesDir path */
     private class TestContext(context: Context, private val folder: File) :
         SysuiTestableContext(context) {
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 23f923a..34d926a 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
@@ -16,38 +16,58 @@
 
 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
 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.coroutines.collectLastValue
 import com.android.systemui.education.data.model.GestureEduModel
 import com.android.systemui.education.data.repository.contextualEducationRepository
 import com.android.systemui.education.data.repository.fakeEduClock
 import com.android.systemui.education.shared.model.EducationUiType
+import com.android.systemui.keyboard.data.repository.keyboardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.data.repository.touchpadRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.hours
 import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val contextualEduInteractor = kosmos.contextualEducationInteractor
+    private val touchpadRepository = kosmos.touchpadRepository
+    private val keyboardRepository = kosmos.keyboardRepository
+    private val userRepository = kosmos.fakeUserRepository
+
     private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor
     private val eduClock = kosmos.fakeEduClock
 
     @Before
     fun setup() {
         underTest.start()
+        contextualEduInteractor.start()
+        userRepository.setUserInfos(USER_INFOS)
     }
 
     @Test
@@ -67,7 +87,6 @@
         }
 
     @Test
-    @kotlinx.coroutines.ExperimentalCoroutinesApi
     fun newEducationNotificationOn2ndEducation() =
         testScope.runTest {
             val model by collectLastValue(underTest.educationTriggered)
@@ -115,10 +134,130 @@
                 )
         }
 
+    @Test
+    fun newTouchpadConnectionTimeOnFirstTouchpadConnected() =
+        testScope.runTest {
+            setIsAnyTouchpadConnected(true)
+            val model = contextualEduInteractor.getEduDeviceConnectionTime()
+            assertThat(model.touchpadFirstConnectionTime).isEqualTo(eduClock.instant())
+        }
+
+    @Test
+    fun unchangedTouchpadConnectionTimeOnSecondConnection() =
+        testScope.runTest {
+            val firstConnectionTime = eduClock.instant()
+            setIsAnyTouchpadConnected(true)
+            setIsAnyTouchpadConnected(false)
+
+            eduClock.offset(1.hours)
+            setIsAnyTouchpadConnected(true)
+
+            val model = contextualEduInteractor.getEduDeviceConnectionTime()
+            assertThat(model.touchpadFirstConnectionTime).isEqualTo(firstConnectionTime)
+        }
+
+    @Test
+    fun newTouchpadConnectionTimeOnUserChanged() =
+        testScope.runTest {
+            // Touchpad connected for user 0
+            setIsAnyTouchpadConnected(true)
+
+            // Change user
+            eduClock.offset(1.hours)
+            val newUserFirstConnectionTime = eduClock.instant()
+            userRepository.setSelectedUserInfo(USER_INFOS[0])
+            runCurrent()
+
+            val model = contextualEduInteractor.getEduDeviceConnectionTime()
+            assertThat(model.touchpadFirstConnectionTime).isEqualTo(newUserFirstConnectionTime)
+        }
+
+    @Test
+    fun newKeyboardConnectionTimeOnKeyboardConnected() =
+        testScope.runTest {
+            setIsAnyKeyboardConnected(true)
+            val model = contextualEduInteractor.getEduDeviceConnectionTime()
+            assertThat(model.keyboardFirstConnectionTime).isEqualTo(eduClock.instant())
+        }
+
+    @Test
+    fun unchangedKeyboardConnectionTimeOnSecondConnection() =
+        testScope.runTest {
+            val firstConnectionTime = eduClock.instant()
+            setIsAnyKeyboardConnected(true)
+            setIsAnyKeyboardConnected(false)
+
+            eduClock.offset(1.hours)
+            setIsAnyKeyboardConnected(true)
+
+            val model = contextualEduInteractor.getEduDeviceConnectionTime()
+            assertThat(model.keyboardFirstConnectionTime).isEqualTo(firstConnectionTime)
+        }
+
+    @Test
+    fun newKeyboardConnectionTimeOnUserChanged() =
+        testScope.runTest {
+            // Keyboard connected for user 0
+            setIsAnyKeyboardConnected(true)
+
+            // Change user
+            eduClock.offset(1.hours)
+            val newUserFirstConnectionTime = eduClock.instant()
+            userRepository.setSelectedUserInfo(USER_INFOS[0])
+            runCurrent()
+
+            val model = contextualEduInteractor.getEduDeviceConnectionTime()
+            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 allAppsKeyGestureEvent =
+                KeyGestureEvent(
+                    /* deviceId= */ 1,
+                    IntArray(0),
+                    KeyEvent.META_META_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
+                )
+            listenerCaptor.value.onKeyGestureEvent(allAppsKeyGestureEvent)
+
+            val model by
+                collectLastValue(
+                    kosmos.contextualEducationRepository.readGestureEduModelFlow(ALL_APPS)
+                )
+            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) {
             contextualEduInteractor.incrementSignalCount(gestureType)
         }
     }
+
+    private fun TestScope.setIsAnyTouchpadConnected(isConnected: Boolean) {
+        touchpadRepository.setIsAnyTouchpadConnected(isConnected)
+        runCurrent()
+    }
+
+    private fun TestScope.setIsAnyKeyboardConnected(isConnected: Boolean) {
+        keyboardRepository.setIsAnyKeyboardConnected(isConnected)
+        runCurrent()
+    }
+
+    companion object {
+        private val USER_INFOS =
+            listOf(
+                UserInfo(101, "Second User", 0),
+            )
+    }
 }
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/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index fd4ed38..686b518 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -23,7 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.haptics.fakeVibratorHelper
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.qs.qsTileFactory
@@ -50,7 +50,7 @@
 
     @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
     private val kosmos = testKosmos()
-    private val vibratorHelper = kosmos.vibratorHelper
+    private val vibratorHelper = kosmos.fakeVibratorHelper
     private val qsTile = kosmos.qsTileFactory.createTile("Test Tile")
     @Mock private lateinit var callback: QSLongPressEffect.Callback
     @Mock private lateinit var controller: ActivityTransitionAnimator.Controller
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index bbfaf6f..6c3c7ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -31,19 +31,19 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
 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
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
@@ -63,27 +63,24 @@
 @RunWith(AndroidJUnit4::class)
 class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
+
     @Mock private lateinit var zenModeController: ZenModeController
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var conditionUri: Uri
     @Mock private lateinit var enableZenModeDialog: EnableZenModeDialog
     @Captor private lateinit var spyZenMode: ArgumentCaptor<Int>
     @Captor private lateinit var spyConditionId: ArgumentCaptor<Uri?>
-    private lateinit var settings: FakeSettings
 
     private lateinit var underTest: DoNotDisturbQuickAffordanceConfig
-    private lateinit var testDispatcher: TestDispatcher
-    private lateinit var testScope: TestScope
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        testDispatcher = StandardTestDispatcher()
-        testScope = TestScope(testDispatcher)
-
-        settings = FakeSettings()
-
         underTest =
             DoNotDisturbQuickAffordanceConfig(
                 context,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 26fcb23..0145f17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -23,19 +23,19 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.kosmos.unconfinedTestScope
 import com.android.systemui.res.R
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.unconfinedDispatcherFakeSettings
 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.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -51,14 +51,15 @@
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.unconfinedTestDispatcher
+    private val testScope = kosmos.unconfinedTestScope
+    private val settings = kosmos.unconfinedDispatcherFakeSettings
+
     @Mock private lateinit var sharedPrefs: FakeSharedPreferences
 
     private lateinit var underTest: KeyguardQuickAffordanceLegacySettingSyncer
-
-    private lateinit var testScope: TestScope
-    private lateinit var testDispatcher: TestDispatcher
     private lateinit var selectionManager: KeyguardQuickAffordanceLocalUserSelectionManager
-    private lateinit var settings: FakeSettings
 
     @Before
     fun setUp() {
@@ -73,8 +74,6 @@
         whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)).thenReturn(true)
         whenever(context.resources).thenReturn(resources)
 
-        testDispatcher = UnconfinedTestDispatcher()
-        testScope = TestScope(testDispatcher)
         selectionManager =
             KeyguardQuickAffordanceLocalUserSelectionManager(
                 context = context,
@@ -92,7 +91,6 @@
                 userTracker = FakeUserTracker(),
                 broadcastDispatcher = fakeBroadcastDispatcher,
             )
-        settings = FakeSettings()
         settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
         settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET, 0)
         settings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index c5ba02d..4e429c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -46,11 +46,10 @@
 import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPRINT
 import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT
 import com.android.systemui.keyguard.shared.model.DevicePosture
-import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
@@ -81,6 +80,8 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidJUnit4::class)
 class BiometricSettingsRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private lateinit var underTest: BiometricSettingsRepository
 
     @Mock private lateinit var authController: AuthController
@@ -88,7 +89,6 @@
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var biometricManager: BiometricManager
-    @Mock private lateinit var tableLogger: TableLogBuffer
     @Captor
     private lateinit var strongAuthTracker: ArgumentCaptor<LockPatternUtils.StrongAuthTracker>
     @Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
@@ -99,7 +99,7 @@
     private lateinit var devicePostureRepository: FakeDevicePostureRepository
     private lateinit var facePropertyRepository: FakeFacePropertyRepository
     private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
-    private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
+    private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
 
     private lateinit var testDispatcher: TestDispatcher
     private lateinit var testScope: TestScope
@@ -115,8 +115,6 @@
         devicePostureRepository = FakeDevicePostureRepository()
         facePropertyRepository = FakeFacePropertyRepository()
         fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
-        mobileConnectionsRepository =
-            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
     }
 
     private suspend fun createBiometricSettingsRepository() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 3a28471..9bcc19d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -369,4 +369,18 @@
             invokeOnCallback { it.onStrongAuthStateChanged(0) }
             assertThat(shouldUpdateIndicatorVisibility).isTrue()
         }
+
+    @Test
+    fun isLockedOut_initialStateFalse() =
+        testScope.runTest {
+            whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+            assertThat(underTest.isLockedOut.value).isEqualTo(false)
+        }
+
+    @Test
+    fun isLockedOut_initialStateTrue() =
+        testScope.runTest {
+            whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
+            assertThat(underTest.isLockedOut.value).isEqualTo(true)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index c85cd66..1582e47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -31,20 +31,21 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.testKosmos
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import java.util.Locale
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
@@ -58,6 +59,11 @@
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
+
     private lateinit var underTest: KeyguardQuickAffordanceRepository
 
     private lateinit var config1: FakeKeyguardQuickAffordanceConfig
@@ -65,7 +71,6 @@
     private lateinit var userTracker: FakeUserTracker
     private lateinit var client1: FakeCustomizationProviderClient
     private lateinit var client2: FakeCustomizationProviderClient
-    private lateinit var testScope: TestScope
 
     @Before
     fun setUp() {
@@ -73,8 +78,6 @@
         context.resources.configuration.setLayoutDirection(Locale.US)
         config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1)
         config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2)
-        val testDispatcher = StandardTestDispatcher()
-        testScope = TestScope(testDispatcher)
         userTracker = FakeUserTracker()
         val localUserSelectionManager =
             KeyguardQuickAffordanceLocalUserSelectionManager(
@@ -128,7 +131,7 @@
                     KeyguardQuickAffordanceLegacySettingSyncer(
                         scope = testScope.backgroundScope,
                         backgroundDispatcher = testDispatcher,
-                        secureSettings = FakeSettings(),
+                        secureSettings = settings,
                         selectionsManager = localUserSelectionManager,
                     ),
                 configs = setOf(config1, config2),
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 783e3b5..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
@@ -150,13 +154,13 @@
     @Test
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
     @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun testTransitionToLockscreen_onPowerButtonPress_canDream_glanceableHubAvailable() =
+    fun testTransitionToLockscreen_onWake_canDream_glanceableHubAvailable() =
         testScope.runTest {
             whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
             kosmos.setCommunalAvailable(true)
             runCurrent()
 
-            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+            powerInteractor.setAwakeForTest()
             runCurrent()
 
             // If dreaming is possible and communal is available, then we should transition to
@@ -170,14 +174,14 @@
 
     @Test
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun testTransitionToLockscreen_onPowerButtonPress_canDream_ktfRefactor() =
+    fun testTransitionToLockscreen_onWake_canDream_ktfRefactor() =
         testScope.runTest {
             whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
             kosmos.setCommunalAvailable(true)
             runCurrent()
             clearInvocations(kosmos.fakeCommunalSceneRepository)
 
-            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+            powerInteractor.setAwakeForTest()
             runCurrent()
 
             // If dreaming is possible and communal is available, then we should transition to
@@ -188,13 +192,13 @@
 
     @Test
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    fun testTransitionToLockscreen_onPowerButtonPress_canNotDream_glanceableHubAvailable() =
+    fun testTransitionToLockscreen_onWake_canNotDream_glanceableHubAvailable() =
         testScope.runTest {
             whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
             kosmos.setCommunalAvailable(true)
             runCurrent()
 
-            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+            powerInteractor.setAwakeForTest()
             runCurrent()
 
             // If dreaming is NOT possible but communal is available, then we should transition to
@@ -208,13 +212,13 @@
 
     @Test
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
-    fun testTransitionToLockscreen_onPowerButtonPress_canNDream_glanceableHubNotAvailable() =
+    fun testTransitionToLockscreen_onWake_canNDream_glanceableHubNotAvailable() =
         testScope.runTest {
             whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
             kosmos.setCommunalAvailable(false)
             runCurrent()
 
-            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
+            powerInteractor.setAwakeForTest()
             runCurrent()
 
             // If dreaming is possible but communal is NOT available, then we should transition to
@@ -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/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index ad07c1c..a8bb2b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -61,7 +61,7 @@
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -80,6 +80,10 @@
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
+
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var userTracker: UserTracker
@@ -90,11 +94,8 @@
     @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
     @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
 
-    private val kosmos = testKosmos()
-
     private lateinit var underTest: KeyguardQuickAffordanceInteractor
 
-    private val testScope = kosmos.testScope
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
@@ -170,7 +171,7 @@
                     KeyguardQuickAffordanceLegacySettingSyncer(
                         scope = testScope.backgroundScope,
                         backgroundDispatcher = kosmos.testDispatcher,
-                        secureSettings = FakeSettings(),
+                        secureSettings = settings,
                         selectionsManager = localUserSelectionManager,
                     ),
                 configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index 2b2c121..aee72de2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -25,8 +25,11 @@
 import com.android.internal.logging.uiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -260,6 +263,23 @@
         }
 
     @Test
+    fun triggersFaceAuthWhenLockscreenIsClicked() =
+        testScope.runTest {
+            collectLastValue(underTest.isMenuVisible)
+            runCurrent()
+            kosmos.fakeDeviceEntryFaceAuthRepository.canRunFaceAuth.value = true
+
+            underTest.onClick(100.0f, 100.0f)
+            runCurrent()
+
+            val runningAuthRequest =
+                kosmos.fakeDeviceEntryFaceAuthRepository.runningAuthRequest.value
+            assertThat(runningAuthRequest?.first)
+                .isEqualTo(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED)
+            assertThat(runningAuthRequest?.second).isEqualTo(true)
+        }
+
+    @Test
     fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() =
         testScope.runTest {
             val isMenuVisible by collectLastValue(underTest.isMenuVisible)
@@ -302,6 +322,7 @@
                 broadcastDispatcher = fakeBroadcastDispatcher,
                 accessibilityManager = kosmos.accessibilityManagerWrapper,
                 pulsingGestureListener = kosmos.pulsingGestureListener,
+                faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
             )
         setUpState()
     }
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..12039c1 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)
@@ -1529,9 +1394,11 @@
 
             kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
             val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            sendSteps(sendStep1)
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
             val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
             val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
-            sendSteps(sendStep1, sendStep2, sendStep3)
+            sendSteps(sendStep2, sendStep3)
 
             assertEquals(listOf(sendStep1, sendStep2), currentStates)
             assertEquals(listOf(sendStep1, sendStep2), currentStatesConverted)
@@ -1545,6 +1412,7 @@
 
             kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
             val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
             val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
             val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
             sendSteps(sendStep1, sendStep2, sendStep3)
@@ -1561,6 +1429,7 @@
 
             kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
             val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
             val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
             val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
             sendSteps(sendStep1, sendStep2, sendStep3)
@@ -1578,6 +1447,7 @@
 
             kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
             val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED)
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
             val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED)
             val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)
             val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
@@ -1596,10 +1466,12 @@
 
             kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone))
             val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            sendSteps(sendStep1)
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
             val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
             val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
             val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
-            sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+            sendSteps(sendStep2, sendStep3, sendStep4)
 
             assertEquals(listOf(sendStep1, sendStep2), currentStates)
             assertEquals(listOf(sendStep1, sendStep2), currentStatesMapped)
@@ -1613,10 +1485,12 @@
 
             kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen))
             val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED)
+            sendSteps(sendStep1)
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
             val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED)
             val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED)
             val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)
-            sendSteps(sendStep1, sendStep2, sendStep3, sendStep4)
+            sendSteps(sendStep2, sendStep3, sendStep4)
 
             assertEquals(listOf<TransitionStep>(), currentStatesMapped)
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 1bc2e24..5a6f2be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1968,47 +1968,6 @@
 
     @Test
     @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToLockscreen_communalKtfRefactor() =
-        testScope.runTest {
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-            runCurrent()
-            clearInvocations(transitionRepository)
-
-            // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalScenes.Communal
-            val targetScene = CommunalScenes.Blank
-
-            val progress = MutableStateFlow(0f)
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = progress,
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalSceneInteractor.setTransitionState(transitionState)
-            progress.value = .1f
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.LOCKSCREEN,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
-    @DisableSceneContainer
     @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToDozing() =
         testScope.runTest {
@@ -2260,54 +2219,6 @@
         }
 
     @Test
-    @DisableSceneContainer
-    @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
-    fun glanceableHubToDreaming_communalKtfRefactor() =
-        testScope.runTest {
-            // GIVEN that we are dreaming and not dozing
-            powerInteractor.setAwakeForTest()
-            keyguardRepository.setDreaming(true)
-            keyguardRepository.setDreamingWithOverlay(true)
-            keyguardRepository.setDozeTransitionModel(
-                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
-            )
-            advanceTimeBy(600L)
-
-            // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
-            runCurrent()
-            clearInvocations(transitionRepository)
-
-            // WHEN a transition away from glanceable hub starts
-            val currentScene = CommunalScenes.Communal
-            val targetScene = CommunalScenes.Blank
-
-            val transitionState =
-                MutableStateFlow<ObservableTransitionState>(
-                    ObservableTransitionState.Transition(
-                        fromScene = currentScene,
-                        toScene = targetScene,
-                        currentScene = flowOf(targetScene),
-                        progress = flowOf(0f, 0.1f),
-                        isInitiatedByUserInput = false,
-                        isUserInputOngoing = flowOf(false),
-                    )
-                )
-            communalSceneInteractor.setTransitionState(transitionState)
-            runCurrent()
-
-            assertThat(transitionRepository)
-                .startedTransition(
-                    ownerName = CommunalSceneTransitionInteractor::class.simpleName,
-                    from = KeyguardState.GLANCEABLE_HUB,
-                    to = KeyguardState.DREAMING,
-                    animatorAssertion = { it.isNull() }, // transition should be manually animated
-                )
-
-            coroutineContext.cancelChildren()
-        }
-
-    @Test
     @BrokenWithSceneContainer(339465026)
     @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
     fun glanceableHubToOccludedDoesNotTriggerWhenDreamStateChanges_communalKtfRefactor() =
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/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6dbe94b..3b2b12c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -360,6 +360,26 @@
         }
 
     @Test
+    @DisableSceneContainer
+    fun alpha_transitionBetweenHubAndDream_isZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha(viewState))
+
+            // Default value check
+            assertThat(alpha).isEqualTo(1f)
+
+            // Start transitioning between DREAM and HUB but don't finish.
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.DREAMING,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = testScope,
+                throughTransitionState = TransitionState.STARTED,
+            )
+
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
     @EnableSceneContainer
     fun alpha_transitionToHub_isZero_scene_container() =
         testScope.runTest {
@@ -369,8 +389,8 @@
                 ObservableTransitionState.Transition(
                     fromScene = Scenes.Lockscreen,
                     toScene = Scenes.Communal,
-                    emptyFlow(),
-                    emptyFlow(),
+                    flowOf(Scenes.Communal),
+                    flowOf(0.5f),
                     false,
                     emptyFlow()
                 )
@@ -485,6 +505,45 @@
         }
 
     @Test
+    @DisableSceneContainer
+    fun alphaFromShadeExpansion_doesNotEmitWhenLockscreenToDreamTransitionRunning() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope,
+            )
+
+            val alpha by collectLastValue(underTest.alpha(viewState))
+            shadeTestUtil.setQsExpansion(0f)
+
+            assertThat(alpha).isEqualTo(1f)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.DREAMING,
+                        transitionState = TransitionState.STARTED,
+                        value = 0f,
+                    ),
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.DREAMING,
+                        transitionState = TransitionState.RUNNING,
+                        value = 0.1f,
+                    ),
+                ),
+                testScope,
+            )
+
+            val alphaBeforeExpansion = alpha
+            shadeTestUtil.setQsExpansion(0.5f)
+            // Alpha should remain unchanged instead of being affected by expansion.
+            assertThat(alpha).isEqualTo(alphaBeforeExpansion)
+        }
+
+    @Test
     fun alpha_shadeClosedOverLockscreen_isOne() =
         testScope.runTest {
             val alpha by collectLastValue(underTest.alpha(viewState))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt
index 86b3f33..0a0ded7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -85,26 +86,22 @@
         testScope.runTest {
             fingerprintPropertyRepository.supportsUdfps()
             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+
             val values by collectValues(underTest.deviceEntryParentViewAlpha)
+            runCurrent()
 
             keyguardTransitionRepository.sendTransitionSteps(
-                listOf(
-                    step(0f, TransitionState.STARTED),
-                    step(0f),
-                    step(0.1f),
-                    step(0.2f),
-                    step(0.3f),
-                    step(1f),
-                ),
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DOZING,
                 testScope,
             )
 
-            values.forEach { assertThat(it).isEqualTo(0f) }
+            assertThat(values[0]).isEqualTo(1f)
+            assertThat(values[1]).isEqualTo(0f)
         }
 
-
     @Test
-    fun lockscreenAlphaFadesOutAndFinishesVisible() =
+    fun lockscreenAlphaDoesNotFadeOut() =
         testScope.runTest {
             val alpha by collectValues(underTest.lockscreenAlpha)
             keyguardTransitionRepository.sendTransitionSteps(
@@ -113,31 +110,7 @@
                 testScope,
             )
 
-            assertThat(alpha[0]).isEqualTo(1f)
-            // Halfway through, it will have faded out
-            assertThat(alpha[1]).isEqualTo(0f)
-            // FINISHED alpha should be visible, to support pulsing
-            assertThat(alpha[2]).isEqualTo(1f)
-        }
-
-    @Test
-    fun deviceEntryBackgroundViewDisappear() =
-        testScope.runTest {
-            val values by collectValues(underTest.deviceEntryBackgroundViewAlpha)
-
-            keyguardTransitionRepository.sendTransitionSteps(
-                listOf(
-                    step(0f, TransitionState.STARTED),
-                    step(0f),
-                    step(0.1f),
-                    step(0.2f),
-                    step(0.3f),
-                    step(1f),
-                ),
-                testScope,
-            )
-
-            values.forEach { assertThat(it).isEqualTo(0f) }
+            alpha.forEach { assertThat(it).isEqualTo(1f) }
         }
 
     private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
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/LockscreenUserActionsViewModelTest.kt
similarity index 93%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index b3ea03e..4253c29 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/LockscreenUserActionsViewModelTest.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
@@ -62,7 +63,7 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 @RunWithLooper
 @EnableSceneContainer
-class LockscreenSceneActionsViewModelTest : SysuiTestCase() {
+class LockscreenUserActionsViewModelTest : SysuiTestCase() {
 
     companion object {
         private const val parameterCount = 6
@@ -169,7 +170,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
-    fun destinationScenes() =
+    fun userActions() =
         testScope.runTest {
             underTest.activateIn(this)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
@@ -192,15 +193,15 @@
                     },
             )
 
-            val destinationScenes by collectLastValue(underTest.actions)
+            val userActions by collectLastValue(underTest.actions)
             val downDestination =
-                destinationScenes?.get(
+                userActions?.get(
                     Swipe(
                         SwipeDirection.Down,
                         fromSource = Edge.Top.takeIf { downFromEdge },
                         pointerCount = if (downWithTwoPointers) 2 else 1,
                     )
-                )
+                ) as? UserActionResult.ChangeScene
             val downScene by
                 collectLastValue(
                     downDestination?.let {
@@ -226,7 +227,8 @@
 
             val upScene by
                 collectLastValue(
-                    destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene?.let { scene ->
+                    (userActions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene?.let {
+                        scene ->
                         kosmos.sceneInteractor.resolveSceneFamily(scene)
                     } ?: flowOf(null)
                 )
@@ -241,7 +243,8 @@
 
             val leftScene by
                 collectLastValue(
-                    destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene?.let { scene ->
+                    (userActions?.get(Swipe.Left) as? UserActionResult.ChangeScene)?.toScene?.let {
+                        scene ->
                         kosmos.sceneInteractor.resolveSceneFamily(scene)
                     } ?: flowOf(null)
                 )
@@ -255,8 +258,8 @@
                 )
         }
 
-    private fun createLockscreenSceneViewModel(): LockscreenSceneActionsViewModel {
-        return LockscreenSceneActionsViewModel(
+    private fun createLockscreenSceneViewModel(): LockscreenUserActionsViewModel {
+        return LockscreenUserActionsViewModel(
             deviceEntryInteractor = kosmos.deviceEntryInteractor,
             communalInteractor = kosmos.communalInteractor,
             shadeInteractor = kosmos.shadeInteractor,
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/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
new file mode 100644
index 0000000..c1dcf37
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -0,0 +1,397 @@
+/**
+ * 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.app.Notification
+import android.app.Notification.MediaStyle
+import android.app.PendingIntent
+import android.app.statusBarManager
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.media.AudioAttributes
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+import android.service.notification.StatusBarNotification
+import androidx.media.utils.MediaConstants
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.Flags.MEDIA_RESUME_PROGRESS
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.model.MediaData
+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.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val KEY = "KEY"
+private const val PACKAGE_NAME = "com.example.app"
+private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
+private const val APP_NAME = "SystemUI"
+private const val SESSION_ARTIST = "artist"
+private const val SESSION_TITLE = "title"
+private const val SESSION_EMPTY_TITLE = ""
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MediaDataLoaderTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val testDispatcher = kosmos.testDispatcher
+    private val statusBarManager = kosmos.statusBarManager
+    private val mediaController = mock<MediaController>()
+    private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
+    private val mediaFlags = kosmos.mediaFlags
+    private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+    private val session = MediaSession(context, "MediaDataLoaderTestSession")
+    private val metadataBuilder =
+        MediaMetadata.Builder().apply {
+            putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+            putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+        }
+
+    private val underTest: MediaDataLoader =
+        MediaDataLoader(
+            context,
+            testDispatcher,
+            testScope,
+            kosmos.activityStarter,
+            mediaControllerFactory,
+            mediaFlags,
+            kosmos.imageLoader,
+            statusBarManager
+        )
+
+    @Before
+    fun setUp() {
+        mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+    }
+
+    @Test
+    fun loadMediaData_returnsMediaData() =
+        testScope.runTest {
+            val song = "THIS_IS_A_SONG"
+            val artist = "THIS_IS_AN_ARTIST"
+            val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+            whenever(mediaController.playbackState)
+                .thenReturn(
+                    PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 12, 1.0f).build()
+                )
+            whenever(mediaController.playbackInfo)
+                .thenReturn(
+                    MediaController.PlaybackInfo(
+                        MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                        0,
+                        0,
+                        0,
+                        AudioAttributes.Builder().build(),
+                        null
+                    )
+                )
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    metadataBuilder
+                        .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, song)
+                        .putString(MediaMetadata.METADATA_KEY_ARTIST, artist)
+                        .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, albumArt)
+                        .putLong(
+                            MediaConstants.METADATA_KEY_IS_EXPLICIT,
+                            MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+                        )
+                        .build()
+                )
+
+            val result = underTest.loadMediaData(KEY, createMediaNotification())
+            assertThat(result).isNotNull()
+            assertThat(result?.appIcon).isNotNull()
+            assertThat(result?.appIcon?.resId).isEqualTo(android.R.drawable.ic_media_pause)
+            assertThat(result?.artist).isEqualTo(artist)
+            assertThat(result?.song).isEqualTo(song)
+            assertThat(result?.artworkIcon).isNotNull()
+            assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(albumArt.width)
+            assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(albumArt.height)
+            assertThat(result?.token).isEqualTo(session.sessionToken)
+            assertThat(result?.device).isNull()
+            assertThat(result?.playbackLocation).isEqualTo(MediaData.PLAYBACK_LOCAL)
+            assertThat(result?.isPlaying).isTrue()
+            assertThat(result?.isExplicit).isTrue()
+            assertThat(result?.resumeAction).isNull()
+            assertThat(result?.resumeProgress).isNull()
+        }
+
+    @Test
+    fun loadMediaDataForResumption_returnsMediaData() =
+        testScope.runTest {
+            fakeFeatureFlags.set(MEDIA_RESUME_PROGRESS, true)
+
+            val song = "THIS_IS_A_SONG"
+            val artist = "THIS_IS_AN_ARTIST"
+            val albumArt = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
+
+            val extras = Bundle()
+            extras.putInt(
+                MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+                MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+            )
+            extras.putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.3)
+            extras.putLong(
+                MediaConstants.METADATA_KEY_IS_EXPLICIT,
+                MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+            )
+
+            val description =
+                MediaDescription.Builder()
+                    .setTitle(song)
+                    .setSubtitle(artist)
+                    .setIconBitmap(albumArt)
+                    .setExtras(extras)
+                    .build()
+
+            val intent =
+                PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+
+            val result =
+                underTest.loadMediaDataForResumption(
+                    0,
+                    description,
+                    Runnable {},
+                    null,
+                    session.sessionToken,
+                    APP_NAME,
+                    intent,
+                    PACKAGE_NAME
+                )
+            assertThat(result).isNotNull()
+            assertThat(result?.appName).isEqualTo(APP_NAME)
+            assertThat(result?.song).isEqualTo(song)
+            assertThat(result?.artist).isEqualTo(artist)
+            assertThat(result?.artworkIcon).isNotNull()
+            assertThat(result?.artworkIcon?.bitmap?.width).isEqualTo(100)
+            assertThat(result?.artworkIcon?.bitmap?.height).isEqualTo(100)
+            assertThat(result?.token).isEqualTo(session.sessionToken)
+            assertThat(result?.clickIntent).isEqualTo(intent)
+            assertThat(result?.isExplicit).isTrue()
+            assertThat(result?.resumeProgress).isEqualTo(0.3)
+        }
+
+    @Test
+    fun loadMediaData_songNameFallbacks() =
+        testScope.runTest {
+            // Check ordering of Song resolution:
+            // DISPLAY_TITLE > TITLE > notification TITLE > notification TITLE_BIG
+
+            // DISPLAY_TITLE
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    MediaMetadata.Builder()
+                        .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, "title1")
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+                        .build()
+                )
+            val result1 = underTest.loadMediaData(KEY, createMediaNotification())
+            assertThat(result1?.song).isEqualTo("title1")
+
+            // TITLE
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    MediaMetadata.Builder()
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, "title2")
+                        .build()
+                )
+            val result2 = underTest.loadMediaData(KEY, createMediaNotification())
+            assertThat(result2?.song).isEqualTo("title2")
+
+            // notification TITLE
+            val notif =
+                SbnBuilder().run {
+                    setPkg(PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                        it.setContentTitle("notiftitle")
+                    }
+                    build()
+                }
+            whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+            val result3 = underTest.loadMediaData(KEY, notif)
+            assertThat(result3?.song).isEqualTo("notiftitle")
+
+            // Final fallback
+            whenever(mediaController.metadata).thenReturn(MediaMetadata.Builder().build())
+            val result4 = underTest.loadMediaData(KEY, createMediaNotification())
+            assertThat(result4?.song)
+                .isEqualTo(context.getString(R.string.controls_media_empty_title, result4?.appName))
+        }
+
+    @Test
+    fun loadMediaData_emptyTitle_hasPlaceholder() =
+        testScope.runTest {
+            val packageManager = mock<PackageManager>()
+            context.setMockPackageManager(packageManager)
+            whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    metadataBuilder
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+                        .build()
+                )
+
+            val result = underTest.loadMediaData(KEY, createMediaNotification())
+
+            val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
+            assertThat(result).isNotNull()
+            assertThat(result?.song).isEqualTo(placeholderTitle)
+        }
+
+    @Test
+    fun loadMediaData_emptyMetadata_usesNotificationTitle() =
+        testScope.runTest {
+            val packageManager = mock<PackageManager>()
+            context.setMockPackageManager(packageManager)
+            whenever(packageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+            whenever(mediaController.metadata)
+                .thenReturn(
+                    metadataBuilder
+                        .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+                        .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE)
+                        .build()
+                )
+            val mediaNotification =
+                SbnBuilder().run {
+                    setPkg(PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.setContentTitle(SESSION_TITLE)
+                        it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                    }
+                    build()
+                }
+
+            val result = underTest.loadMediaData(KEY, mediaNotification)
+
+            assertThat(result).isNotNull()
+            assertThat(result?.song).isEqualTo(SESSION_TITLE)
+        }
+
+    @Test
+    fun loadMediaData_badArtwork_isNotUsed() =
+        testScope.runTest {
+            val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+            val mediaNotification =
+                SbnBuilder().run {
+                    setPkg(PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                        it.setLargeIcon(artwork)
+                    }
+                    build()
+                }
+
+            val result = underTest.loadMediaData(KEY, mediaNotification)
+
+            assertThat(result).isNotNull()
+        }
+
+    @Test
+    fun loadMediaData_invalidTokenNoCrash() =
+        testScope.runTest {
+            val bundle = Bundle()
+            // wrong data type
+            bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle())
+            val rcn =
+                SbnBuilder().run {
+                    setPkg(SYSTEM_PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.addExtras(bundle)
+                        it.setStyle(
+                            MediaStyle().apply { setRemotePlaybackInfo("Remote device", 0, null) }
+                        )
+                    }
+                    build()
+                }
+
+            val result = underTest.loadMediaData(KEY, rcn)
+            assertThat(result).isNull()
+        }
+
+    @Test
+    fun testLoadMediaDataInBg_invalidMediaRemoteIntentNoCrash() =
+        testScope.runTest {
+            val bundle = Bundle()
+            // wrong data type
+            bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle())
+            val rcn =
+                SbnBuilder().run {
+                    setPkg(SYSTEM_PACKAGE_NAME)
+                    modifyNotification(context).also {
+                        it.setSmallIcon(android.R.drawable.ic_media_pause)
+                        it.addExtras(bundle)
+                        it.setStyle(
+                            MediaStyle().apply {
+                                setMediaSession(session.sessionToken)
+                                setRemotePlaybackInfo("Remote device", 0, null)
+                            }
+                        )
+                    }
+                    build()
+                }
+
+            val result = underTest.loadMediaData(KEY, rcn)
+            assertThat(result).isNotNull()
+        }
+
+    private fun createMediaNotification(
+        mediaSession: MediaSession? = session,
+        applicationInfo: ApplicationInfo? = null
+    ): StatusBarNotification =
+        SbnBuilder().run {
+            setPkg(PACKAGE_NAME)
+            modifyNotification(context).also {
+                it.setSmallIcon(android.R.drawable.ic_media_pause)
+                it.setStyle(MediaStyle().apply { setMediaSession(mediaSession?.sessionToken) })
+                if (applicationInfo != null) {
+                    val bundle = Bundle()
+                    bundle.putParcelable(
+                        Notification.EXTRA_BUILDER_APPLICATION_INFO,
+                        applicationInfo
+                    )
+                    it.addExtras(bundle)
+                }
+            }
+            build()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
new file mode 100644
index 0000000..f6865f13
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.notifications.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+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.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayActionsViewModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val underTest = kosmos.notificationsShadeOverlayActionsViewModel
+
+    @Test
+    fun upTransitionSceneKey_hidesShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            underTest.activateIn(this)
+
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.HideOverlay)?.overlay)
+                .isEqualTo(Overlays.NotificationsShade)
+            assertThat(actions?.get(Swipe.Down)).isNull()
+        }
+
+    @Test
+    fun back_hidesShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            underTest.activateIn(this)
+
+            assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay)
+                .isEqualTo(Overlays.NotificationsShade)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
new file mode 100644
index 0000000..88a1df1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.notifications.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
+
+    private val underTest = kosmos.notificationsShadeOverlayContentViewModel
+
+    @Test
+    fun onScrimClicked_hidesShade() =
+        testScope.runTest {
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            sceneInteractor.showOverlay(
+                overlay = Overlays.NotificationsShade,
+                loggingReason = "test",
+            )
+            assertThat(currentOverlays).contains(Overlays.NotificationsShade)
+
+            underTest.onScrimClicked()
+
+            assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
+        }
+}
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/NotificationsShadeUserActionsViewModelTest.kt
similarity index 76%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModelTest.kt
index 8f925d5..46b02e92 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/NotificationsShadeUserActionsViewModelTest.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
@@ -36,8 +37,7 @@
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.fakeShadeRepository
-import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneActionsViewModel
+import com.android.systemui.shade.ui.viewmodel.notificationsShadeUserActionsViewModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -52,14 +52,14 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
-class NotificationsShadeSceneActionsViewModelTest : SysuiTestCase() {
+class NotificationsShadeUserActionsViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
 
-    private val underTest by lazy { kosmos.notificationsShadeSceneActionsViewModel }
+    private val underTest by lazy { kosmos.notificationsShadeUserActionsViewModel }
 
     @Test
     fun upTransitionSceneKey_deviceLocked_lockscreen() =
@@ -68,7 +68,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 +83,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,40 +96,13 @@
             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)
         }
 
     @Test
-    fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
-        testScope.runTest {
-            kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
-            val actions by collectLastValue(underTest.actions)
-            lockDevice()
-            underTest.activateIn(this)
-
-            assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(actions?.get(Swipe.Up)).isNull()
-            assertThat(kosmos.homeSceneFamilyResolver.resolvedScene.value)
-                .isEqualTo(Scenes.Lockscreen)
-        }
-
-    @Test
-    fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
-        testScope.runTest {
-            kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
-            val actions by collectLastValue(underTest.actions)
-            lockDevice()
-            unlockDevice()
-            underTest.activateIn(this)
-
-            assertThat(actions?.get(Swipe.Down)?.toScene).isEqualTo(SceneFamilies.Home)
-            assertThat(actions?.get(Swipe.Up)).isNull()
-            assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
-        }
-
-    @Test
     fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
             val actions by collectLastValue(underTest.actions)
@@ -138,7 +113,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)
         }
@@ -146,17 +122,20 @@
     @Test
     fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
+            val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
             val actions by collectLastValue(underTest.actions)
             kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.None
             )
+            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
             sceneInteractor // force the lazy; this will kick off StateFlows
             runCurrent()
             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 5999265..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
@@ -31,6 +32,10 @@
 import com.android.systemui.qs.fgsManagerController
 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
 import kotlinx.coroutines.Dispatchers
@@ -140,6 +145,59 @@
             }
         }
 
+    @Test
+    fun statusBarState_followsController() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val statusBarState by collectLastValue(underTest.statusBarState)
+                runCurrent()
+
+                sysuiStatusBarStateController.setState(StatusBarState.SHADE)
+                assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
+
+                sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD)
+                assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+                sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+                assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
+            }
+        }
+
+    @Test
+    fun statusBarState_changesEarlyIfUpcomingStateIsKeyguard() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val statusBarState by collectLastValue(underTest.statusBarState)
+
+                sysuiStatusBarStateController.setState(StatusBarState.SHADE)
+                sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE_LOCKED)
+                assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
+
+                sysuiStatusBarStateController.setUpcomingState(StatusBarState.KEYGUARD)
+                assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+                sysuiStatusBarStateController.setUpcomingState(StatusBarState.SHADE)
+                assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+            }
+        }
+
+    @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 {
@@ -148,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/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
index d153e9d..5619022 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -21,14 +21,15 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
 import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,10 +39,11 @@
 @RunWith(AndroidJUnit4::class)
 class AutoAddableSettingTest : SysuiTestCase() {
 
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val secureSettings = kosmos.fakeSettings
 
-    private val secureSettings = FakeSettings()
     private val underTest =
         AutoAddableSetting(
             secureSettings,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
index 79fcc92..d27e810 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
@@ -29,8 +29,9 @@
 import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -40,8 +41,9 @@
 @EnabledOnRavenwood
 @RunWith(AndroidJUnit4::class)
 class AirplaneModeTileUserActionInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
-    private val mobileConnectionsRepository = FakeMobileConnectionsRepository()
+    private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
     private val connectivityRepository = FakeConnectivityRepository()
     private val airplaneModeRepository = FakeAirplaneModeRepository()
     private val inputHandler = FakeQSTileIntentUserInputHandler()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
index 1ea8abc..6f11b2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
 import com.android.systemui.res.R
@@ -86,7 +86,7 @@
     private val wifiInteractor =
         WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
 
-    private val tableLogBuffer: TableLogBuffer = mock()
+    private val tableLogBuffer = logcatTableLogBuffer(kosmos, "InternetTileDataInteractorTest")
     private val carrierConfigTracker: CarrierConfigTracker = mock()
 
     private val mobileConnectionsRepository =
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 94fa9b9..91d8e2a 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,16 +24,17 @@
 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.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
-import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
-import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,7 +45,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -55,18 +55,14 @@
     private val dispatcher = kosmos.testDispatcher
     private val zenModeRepository = kosmos.fakeZenModeRepository
 
-    private val underTest =
-        ModesTileDataInteractor(
-            context,
-            ZenModeInteractor(zenModeRepository, mock<NotificationSettingsRepository>()),
-            dispatcher
-        )
+    private val underTest = ModesTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher)
 
     @Before
     fun setUp() {
         context.orCreateTestableResources.apply {
-            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)
+            addOverride(MODES_DRAWABLE_ID, MODES_DRAWABLE)
+            addOverride(R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE)
+            addOverride(R.drawable.ic_zen_mode_type_driving, DRIVING_DRAWABLE)
         }
     }
 
@@ -132,28 +128,34 @@
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
-    fun changesIconWhenActiveModesChange() =
+    fun tileData_iconsFlagEnabled_changesIconWhenActiveModesChange() =
         testScope.runTest {
-            val dataList: List<ModesTileModel> by
-                collectValues(
+            val tileData by
+                collectLastValue(
                     underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
                 )
-            runCurrent()
-            assertThat(dataList.map { it.icon }).containsExactly(null).inOrder()
 
-            // Add an inactive mode: state hasn't changed, so this shouldn't cause another emission
+            // Tile starts with the generic Modes icon.
+            runCurrent()
+            assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+
+            // Add an inactive mode -> Still modes icon
             zenModeRepository.addMode(id = "Mode", active = false)
             runCurrent()
-            assertThat(dataList.map { it.icon }).containsExactly(null).inOrder()
+            assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
 
-            // Add an active mode: icon should be the mode icon
+            // Add an active mode: icon should be the mode icon. No iconResId, because we don't
+            // really know that it's a system icon.
             zenModeRepository.addMode(
                 id = "Bedtime",
                 type = AutomaticZenRule.TYPE_BEDTIME,
                 active = true
             )
             runCurrent()
-            assertThat(dataList.map { it.icon }).containsExactly(null, BEDTIME_ICON).inOrder()
+            assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
+            assertThat(tileData?.iconResId).isNull()
 
             // Add another, less-prioritized mode: icon should remain the first mode icon
             zenModeRepository.addMode(
@@ -162,29 +164,91 @@
                 active = true
             )
             runCurrent()
-            assertThat(dataList.map { it.icon })
-                .containsExactly(null, BEDTIME_ICON, BEDTIME_ICON)
-                .inOrder()
+            assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON)
+            assertThat(tileData?.iconResId).isNull()
 
-            // Deactivate more important mode: icon should be the less important, still active mode.
+            // Deactivate more important mode: icon should be the less important, still active mode
             zenModeRepository.deactivateMode("Bedtime")
             runCurrent()
-            assertThat(dataList.map { it.icon })
-                .containsExactly(null, BEDTIME_ICON, BEDTIME_ICON, DRIVING_ICON)
-                .inOrder()
+            assertThat(tileData?.icon).isEqualTo(DRIVING_ICON)
+            assertThat(tileData?.iconResId).isNull()
 
-            // Deactivate remaining mode: no icon
+            // Deactivate remaining mode: back to the default modes icon
             zenModeRepository.deactivateMode("Driving")
             runCurrent()
-            assertThat(dataList.map { it.icon })
-                .containsExactly(null, BEDTIME_ICON, BEDTIME_ICON, DRIVING_ICON, null)
-                .inOrder()
+            assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
         }
 
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    @DisableFlags(Flags.FLAG_MODES_UI_ICONS)
+    fun tileData_iconsFlagDisabled_hasPriorityModesIcon() =
+        testScope.runTest {
+            val tileData by
+                collectLastValue(
+                    underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            runCurrent()
+            assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+
+            // Activate a Mode -> Icon doesn't change.
+            zenModeRepository.addMode(id = "Mode", active = true)
+            runCurrent()
+            assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+
+            zenModeRepository.deactivateMode(id = "Mode")
+            runCurrent()
+            assertThat(tileData?.icon).isEqualTo(MODES_ICON)
+            assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
+        }
+
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    @Test
+    fun getCurrentTileModel_returnsActiveModes() = runTest {
+        var tileData = underTest.getCurrentTileModel()
+        assertThat(tileData.isActivated).isFalse()
+        assertThat(tileData.activeModes).isEmpty()
+
+        // Add active mode
+        zenModeRepository.addMode(id = "One", active = true)
+        tileData = underTest.getCurrentTileModel()
+        assertThat(tileData.isActivated).isTrue()
+        assertThat(tileData.activeModes).containsExactly("Mode One")
+
+        // Add an inactive mode: state hasn't changed
+        zenModeRepository.addMode(id = "Two", active = false)
+        tileData = underTest.getCurrentTileModel()
+        assertThat(tileData.isActivated).isTrue()
+        assertThat(tileData.activeModes).containsExactly("Mode One")
+
+        // Add another active mode
+        zenModeRepository.addMode(id = "Three", active = true)
+        tileData = underTest.getCurrentTileModel()
+        assertThat(tileData.isActivated).isTrue()
+        assertThat(tileData.activeModes).containsExactly("Mode One", "Mode Three").inOrder()
+
+        // Remove a mode and deactivate the other
+        zenModeRepository.removeMode("One")
+        zenModeRepository.deactivateMode("Three")
+        tileData = underTest.getCurrentTileModel()
+        assertThat(tileData.isActivated).isFalse()
+        assertThat(tileData.activeModes).isEmpty()
+    }
+
     private companion object {
         val TEST_USER = UserHandle.of(1)!!
+
+        val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes
+
+        val MODES_DRAWABLE = TestStubDrawable("modes_icon")
         val BEDTIME_DRAWABLE = TestStubDrawable("bedtime")
         val DRIVING_DRAWABLE = TestStubDrawable("driving")
+
+        val MODES_ICON = MODES_DRAWABLE.asIcon()
         val BEDTIME_ICON = BEDTIME_DRAWABLE.asIcon()
         val DRIVING_ICON = DRIVING_DRAWABLE.asIcon()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index 4b75649..cd58127 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.qs.tiles.impl.modes.domain.interactor
 
+import android.graphics.drawable.TestStubDrawable
 import android.platform.test.annotations.EnableFlags
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
 import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
@@ -54,10 +56,7 @@
     fun handleClick_active() = runTest {
         val expandable = mock<Expandable>()
         underTest.handleInput(
-            QSTileInputTestKtx.click(
-                data = ModesTileModel(true, listOf("DND")),
-                expandable = expandable
-            )
+            QSTileInputTestKtx.click(data = modelOf(true, listOf("DND")), expandable = expandable)
         )
 
         verify(mockDialogDelegate).showDialog(eq(expandable))
@@ -67,10 +66,7 @@
     fun handleClick_inactive() = runTest {
         val expandable = mock<Expandable>()
         underTest.handleInput(
-            QSTileInputTestKtx.click(
-                data = ModesTileModel(false, emptyList()),
-                expandable = expandable
-            )
+            QSTileInputTestKtx.click(data = modelOf(false, emptyList()), expandable = expandable)
         )
 
         verify(mockDialogDelegate).showDialog(eq(expandable))
@@ -78,7 +74,7 @@
 
     @Test
     fun handleLongClick_active() = runTest {
-        underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true, listOf("DND"))))
+        underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(true, listOf("DND"))))
 
         QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
             assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
@@ -87,10 +83,14 @@
 
     @Test
     fun handleLongClick_inactive() = runTest {
-        underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false, emptyList())))
+        underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(false, emptyList())))
 
         QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
             assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
         }
     }
+
+    private fun modelOf(isActivated: Boolean, activeModeNames: List<String>): ModesTileModel {
+        return ModesTileModel(isActivated, activeModeNames, TestStubDrawable("icon").asIcon(), 123)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index a41f15d..f7bdcb8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -18,11 +18,12 @@
 
 import android.app.Flags
 import android.graphics.drawable.TestStubDrawable
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
@@ -58,47 +59,88 @@
 
     @Test
     fun inactiveState() {
-        val model = ModesTileModel(isActivated = false, activeModes = emptyList())
+        val icon = TestStubDrawable("res123").asIcon()
+        val model =
+            ModesTileModel(
+                isActivated = false,
+                activeModes = emptyList(),
+                icon = icon,
+            )
 
         val state = underTest.map(config, model)
 
         assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
-        assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_off)
+        assertThat(state.icon()).isEqualTo(icon)
         assertThat(state.secondaryLabel).isEqualTo("No active modes")
     }
 
     @Test
     fun activeState_oneMode() {
-        val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"))
+        val icon = TestStubDrawable("res123").asIcon()
+        val model =
+            ModesTileModel(
+                isActivated = true,
+                activeModes = listOf("DND"),
+                icon = icon,
+            )
 
         val state = underTest.map(config, model)
 
         assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
-        assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on)
+        assertThat(state.icon()).isEqualTo(icon)
         assertThat(state.secondaryLabel).isEqualTo("DND is active")
     }
 
     @Test
     fun activeState_multipleModes() {
+        val icon = TestStubDrawable("res123").asIcon()
         val model =
-            ModesTileModel(isActivated = true, activeModes = listOf("Mode 1", "Mode 2", "Mode 3"))
+            ModesTileModel(
+                isActivated = true,
+                activeModes = listOf("Mode 1", "Mode 2", "Mode 3"),
+                icon = icon,
+            )
 
         val state = underTest.map(config, model)
 
         assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
-        assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on)
+        assertThat(state.icon()).isEqualTo(icon)
         assertThat(state.secondaryLabel).isEqualTo("3 modes are active")
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_UI_ICONS)
-    fun activeState_withIcon() {
-        val icon = Icon.Resource(1234, contentDescription = null)
-        val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"), icon = icon)
+    fun state_withEnabledFlag_noIconResId() {
+        val icon = TestStubDrawable("res123").asIcon()
+        val model =
+            ModesTileModel(
+                isActivated = false,
+                activeModes = emptyList(),
+                icon = icon,
+                iconResId = 123 // Should not be populated, but is ignored even if present
+            )
 
         val state = underTest.map(config, model)
 
-        assertThat(state.iconRes).isNull()
         assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.iconRes).isNull()
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI_ICONS)
+    fun state_withDisabledFlag_includesIconResId() {
+        val icon = TestStubDrawable("res123").asIcon()
+        val model =
+            ModesTileModel(
+                isActivated = false,
+                activeModes = emptyList(),
+                icon = icon,
+                iconResId = 123
+            )
+
+        val state = underTest.map(config, model)
+
+        assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.iconRes).isEqualTo(123)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index d472d98..22913f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -255,7 +255,7 @@
             runCurrent()
             clearInvocations(qsImpl!!)
 
-            underTest.setState(QSSceneAdapter.State.Expanding(progress))
+            underTest.setState(QSSceneAdapter.State.Expanding { progress })
             with(qsImpl!!) {
                 verify(this).setQsVisible(true)
                 verify(this, never())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
index 63ce67c..41b5988 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
@@ -20,7 +20,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.ui.adapter.ExpandingSubject.Companion.assertThatExpanding
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth.assertAbout
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -32,33 +37,59 @@
 
     @Test
     fun expanding_squishiness1() {
-        assertThat(QSSceneAdapter.State.Expanding(0.3f).squishiness()).isEqualTo(1f)
+        assertThat(QSSceneAdapter.State.Expanding { 0.3f }.squishiness()).isEqualTo(1f)
     }
 
     @Test
     fun expandingSpecialValues() {
-        assertThat(QSSceneAdapter.State.QQS).isEqualTo(QSSceneAdapter.State.Expanding(0f))
-        assertThat(QSSceneAdapter.State.QS).isEqualTo(QSSceneAdapter.State.Expanding(1f))
+        assertThatExpanding(QSSceneAdapter.State.QQS)
+            .isEqualTo(QSSceneAdapter.State.Expanding { 0f })
+        assertThatExpanding(QSSceneAdapter.State.QS)
+            .isEqualTo(QSSceneAdapter.State.Expanding { 1f })
     }
 
     @Test
     fun collapsing() {
         val collapsingProgress = 0.3f
-        assertThat(Collapsing(collapsingProgress))
-            .isEqualTo(QSSceneAdapter.State.Expanding(1 - collapsingProgress))
+        assertThatExpanding(Collapsing { collapsingProgress })
+            .isEqualTo(QSSceneAdapter.State.Expanding { 1 - collapsingProgress })
     }
 
     @Test
     fun unsquishingQQS_expansionSameAsQQS() {
         val squishiness = 0.6f
-        assertThat(QSSceneAdapter.State.UnsquishingQQS { squishiness }.expansion)
-            .isEqualTo(QSSceneAdapter.State.QQS.expansion)
+        assertThat(QSSceneAdapter.State.UnsquishingQQS { squishiness }.expansion())
+            .isEqualTo(QSSceneAdapter.State.QQS.expansion())
     }
 
     @Test
     fun unsquishingQS_expansionSameAsQS() {
         val squishiness = 0.6f
-        assertThat(QSSceneAdapter.State.UnsquishingQS { squishiness }.expansion)
-            .isEqualTo(QSSceneAdapter.State.QS.expansion)
+        assertThat(QSSceneAdapter.State.UnsquishingQS { squishiness }.expansion())
+            .isEqualTo(QSSceneAdapter.State.QS.expansion())
+    }
+}
+
+private class ExpandingSubject(
+    metadata: FailureMetadata,
+    private val actual: QSSceneAdapter.State.Expanding?
+) : Subject(metadata, actual) {
+    fun isEqualTo(expected: QSSceneAdapter.State.Expanding) {
+        isNotNull()
+        check("expansion()")
+            .that(actual?.expansion?.invoke())
+            .isEqualTo(expected.expansion.invoke())
+    }
+
+    companion object {
+        fun expanding(): Factory<ExpandingSubject, QSSceneAdapter.State.Expanding> {
+            return Factory { metadata: FailureMetadata, actual: QSSceneAdapter.State.Expanding? ->
+                ExpandingSubject(metadata, actual)
+            }
+        }
+
+        fun assertThatExpanding(actual: QSSceneAdapter.State.Expanding): ExpandingSubject {
+            return assertAbout(expanding()).that(actual)
+        }
     }
 }
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..424afe1 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.ui.viewmodel
 
+import android.platform.test.annotations.DisableFlags
 import android.testing.TestableLooper.RunWithLooper
 import androidx.lifecycle.LifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -33,14 +34,19 @@
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -52,6 +58,7 @@
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
 @EnableSceneContainer
+@DisableFlags(com.android.systemui.Flags.FLAG_DUAL_SHADE)
 class QuickSettingsSceneContentViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
@@ -65,6 +72,8 @@
     private val footerActionsController = mock<FooterActionsController>()
 
     private val sceneContainerStartable = kosmos.sceneContainerStartable
+    private val sceneInteractor by lazy { kosmos.sceneInteractor }
+    private val shadeInteractor by lazy { kosmos.shadeInteractor }
 
     private lateinit var underTest: QuickSettingsSceneContentViewModel
 
@@ -81,6 +90,8 @@
                 footerActionsViewModelFactory = footerActionsViewModelFactory,
                 footerActionsController = footerActionsController,
                 mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
+                shadeInteractor = shadeInteractor,
+                sceneInteractor = sceneInteractor,
             )
         underTest.activateIn(testScope)
     }
@@ -124,4 +135,16 @@
 
             assertThat(isMediaVisible).isTrue()
         }
+
+    @Test
+    fun shadeModeChange_switchToShadeScene() =
+        testScope.runTest {
+            val scene by collectLastValue(sceneInteractor.currentScene)
+
+            // switch to split shade
+            kosmos.shadeRepository.setShadeLayoutWide(true)
+            runCurrent()
+
+            assertThat(scene).isEqualTo(Scenes.Shade)
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
new file mode 100644
index 0000000..762941d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.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.qs.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+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.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val underTest = kosmos.quickSettingsShadeOverlayActionsViewModel
+
+    @Test
+    fun upTransitionSceneKey_hidesShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            underTest.activateIn(this)
+
+            assertThat((actions?.get(Swipe.Up) as? UserActionResult.HideOverlay)?.overlay)
+                .isEqualTo(Overlays.QuickSettingsShade)
+            assertThat(actions?.get(Swipe.Down)).isNull()
+        }
+
+    @Test
+    fun back_hidesShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            underTest.activateIn(this)
+
+            assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay)
+                .isEqualTo(Overlays.QuickSettingsShade)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
new file mode 100644
index 0000000..abd1e2c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -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.systemui.qs.ui.viewmodel
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sceneInteractor = kosmos.sceneInteractor
+
+    private val underTest = kosmos.quickSettingsShadeOverlayContentViewModel
+
+    @Test
+    fun onScrimClicked_hidesShade() =
+        testScope.runTest {
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            sceneInteractor.showOverlay(
+                overlay = Overlays.QuickSettingsShade,
+                loggingReason = "test",
+            )
+            assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)
+
+            underTest.onScrimClicked()
+
+            assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
+        }
+}
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/QuickSettingsShadeUserActionsViewModelTest.kt
similarity index 76%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelTest.kt
index 647fdf6..32772d2 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/QuickSettingsShadeUserActionsViewModelTest.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
@@ -38,14 +39,12 @@
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.testKosmos
 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
 
@@ -54,28 +53,25 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
-class QuickSettingsShadeSceneActionsViewModelTest : SysuiTestCase() {
+class QuickSettingsShadeUserActionsViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneInteractor = kosmos.sceneInteractor
     private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
 
-    private val underTest by lazy { kosmos.quickSettingsShadeSceneActionsViewModel }
-
-    @Before
-    fun setUp() {
-        underTest.activateIn(testScope)
-    }
+    private val underTest by lazy { kosmos.quickSettingsShadeUserActionsViewModel }
 
     @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,58 +79,36 @@
     @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)
         }
 
     @Test
-    fun downTransitionSceneKey_deviceLocked_bottomAligned_lockscreen() =
-        testScope.runTest {
-            kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
-            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.Up)).isNull()
-            assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
-        }
-
-    @Test
-    fun downTransitionSceneKey_deviceUnlocked_bottomAligned_gone() =
-        testScope.runTest {
-            kosmos.fakeShadeRepository.setDualShadeAlignedToBottom(true)
-            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.Up)).isNull()
-            assertThat(homeScene).isEqualTo(Scenes.Gone)
-        }
-
-    @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 +117,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 +135,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/qs/ui/viewmodel/QuickSettingsSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
similarity index 98%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
index f26a9db..6986cf8e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModelTest.kt
@@ -57,7 +57,7 @@
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
 @EnableSceneContainer
-class QuickSettingsSceneActionsViewModelTest : SysuiTestCase() {
+class QuickSettingsUserActionsViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
@@ -67,7 +67,7 @@
     private val sceneBackInteractor = kosmos.sceneBackInteractor
     private val sceneContainerStartable = kosmos.sceneContainerStartable
 
-    private lateinit var underTest: QuickSettingsSceneActionsViewModel
+    private lateinit var underTest: QuickSettingsUserActionsViewModel
 
     @Before
     fun setUp() {
@@ -75,7 +75,7 @@
 
         sceneContainerStartable.start()
         underTest =
-            QuickSettingsSceneActionsViewModel(
+            QuickSettingsUserActionsViewModel(
                 qsSceneAdapter = qsFlexiglassAdapter,
                 sceneBackInteractor = sceneBackInteractor,
             )
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 c15a4e5..4f7c013 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
@@ -48,7 +49,7 @@
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneActionsViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenUserActionsViewModel
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -58,15 +59,16 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.logger.sceneLogger
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneActionsViewModel
 import com.android.systemui.shade.ui.viewmodel.ShadeSceneContentViewModel
-import com.android.systemui.shade.ui.viewmodel.shadeSceneActionsViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeUserActionsViewModel
 import com.android.systemui.shade.ui.viewmodel.shadeSceneContentViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeUserActionsViewModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
@@ -133,6 +135,7 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                logger = kosmos.sceneLogger,
                 motionEventHandlerReceiver = {},
             )
             .apply { setTransitionState(transitionState) }
@@ -142,8 +145,8 @@
     private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
     private lateinit var bouncerSceneContentViewModel: BouncerSceneContentViewModel
 
-    private val lockscreenSceneActionsViewModel by lazy {
-        LockscreenSceneActionsViewModel(
+    private val mLockscreenUserActionsViewModel by lazy {
+        LockscreenUserActionsViewModel(
             deviceEntryInteractor = deviceEntryInteractor,
             communalInteractor = communalInteractor,
             shadeInteractor = kosmos.shadeInteractor,
@@ -151,7 +154,7 @@
     }
 
     private lateinit var shadeSceneContentViewModel: ShadeSceneContentViewModel
-    private lateinit var shadeSceneActionsViewModel: ShadeSceneActionsViewModel
+    private lateinit var mShadeUserActionsViewModel: ShadeUserActionsViewModel
 
     private val powerInteractor by lazy { kosmos.powerInteractor }
 
@@ -173,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
@@ -191,14 +191,14 @@
         bouncerSceneContentViewModel = kosmos.bouncerSceneContentViewModel
 
         shadeSceneContentViewModel = kosmos.shadeSceneContentViewModel
-        shadeSceneActionsViewModel = kosmos.shadeSceneActionsViewModel
+        mShadeUserActionsViewModel = kosmos.shadeUserActionsViewModel
 
         val startable = kosmos.sceneContainerStartable
         startable.start()
 
-        lockscreenSceneActionsViewModel.activateIn(testScope)
+        mLockscreenUserActionsViewModel.activateIn(testScope)
         shadeSceneContentViewModel.activateIn(testScope)
-        shadeSceneActionsViewModel.activateIn(testScope)
+        mShadeUserActionsViewModel.activateIn(testScope)
         bouncerSceneContentViewModel.activateIn(testScope)
         sceneContainerViewModel.activateIn(testScope)
 
@@ -229,8 +229,9 @@
     @Test
     fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
         testScope.runTest {
-            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -249,8 +250,9 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
-            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -260,7 +262,7 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         testScope.runTest {
-            val actions by collectLastValue(shadeSceneActionsViewModel.actions)
+            val actions by collectLastValue(mShadeUserActionsViewModel.actions)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
             assertCurrentScene(Scenes.Lockscreen)
@@ -269,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(
@@ -280,7 +283,7 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
         testScope.runTest {
-            val actions by collectLastValue(shadeSceneActionsViewModel.actions)
+            val actions by collectLastValue(mShadeUserActionsViewModel.actions)
             val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
             val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
 
@@ -297,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(
@@ -365,8 +369,9 @@
     fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
         testScope.runTest {
             unlockDevice()
-            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
         }
 
@@ -387,8 +392,9 @@
     fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(
                 to = upDestinationSceneKey,
@@ -405,8 +411,9 @@
     fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
@@ -425,8 +432,9 @@
         testScope.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
             startPhoneCall()
-            val actions by collectLastValue(lockscreenSceneActionsViewModel.actions)
-            val upDestinationSceneKey = actions?.get(Swipe.Up)?.toScene
+            val actions by collectLastValue(mLockscreenUserActionsViewModel.actions)
+            val upDestinationSceneKey =
+                (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
@@ -493,7 +501,9 @@
     private fun getCurrentSceneInUi(): SceneKey {
         return when (val state = transitionState.value) {
             is ObservableTransitionState.Idle -> state.currentScene
-            is ObservableTransitionState.Transition -> 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 64a13de..ec79cc6 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
@@ -19,6 +19,7 @@
 package com.android.systemui.scene.domain.startable
 
 import android.app.StatusBarManager
+import android.hardware.face.FaceManager
 import android.os.PowerManager
 import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -30,6 +31,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
@@ -39,8 +43,14 @@
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
@@ -51,13 +61,17 @@
 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.BiometricUnlockMode
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.model.sysUiState
 import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.data.repository.powerRepository
 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
@@ -69,6 +83,7 @@
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
@@ -85,6 +100,7 @@
 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
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -92,6 +108,8 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
@@ -105,12 +123,14 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
+    private val deviceEntryHapticsInteractor by lazy { kosmos.deviceEntryHapticsInteractor }
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
     private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
     private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
     private val sysUiState = kosmos.sysUiState
     private val falsingCollector = mock<FalsingCollector>().also { kosmos.falsingCollector = it }
+    private val vibratorHelper = mock<VibratorHelper>().also { kosmos.vibratorHelper = it }
     private val fakeSceneDataSource = kosmos.fakeSceneDataSource
     private val windowController = kosmos.notificationShadeWindowController
     private val centralSurfaces = kosmos.centralSurfaces
@@ -551,8 +571,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 +603,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 =
@@ -636,6 +654,194 @@
         }
 
     @Test
+    fun playSuccessHaptics_onSuccessfulLockscreenAuth_udfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playSuccessHaptic by
+                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+
+            setupBiometricAuth(hasUdfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            unlockWithFingerprintAuth()
+
+            assertThat(playSuccessHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            verify(vibratorHelper)
+                .vibrateAuthSuccess(
+                    "SceneContainerStartable, $currentSceneKey device-entry::success"
+                )
+            verify(vibratorHelper, never()).vibrateAuthError(anyString())
+
+            updateFingerprintAuthStatus(isSuccess = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun playSuccessHaptics_onSuccessfulLockscreenAuth_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playSuccessHaptic by
+                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            allowHapticsOnSfps()
+            unlockWithFingerprintAuth()
+
+            assertThat(playSuccessHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            verify(vibratorHelper)
+                .vibrateAuthSuccess(
+                    "SceneContainerStartable, $currentSceneKey device-entry::success"
+                )
+            verify(vibratorHelper, never()).vibrateAuthError(anyString())
+
+            updateFingerprintAuthStatus(isSuccess = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun playErrorHaptics_onFailedLockscreenAuth_udfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
+
+            setupBiometricAuth(hasUdfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            updateFingerprintAuthStatus(isSuccess = false)
+
+            assertThat(playErrorHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            verify(vibratorHelper)
+                .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
+            verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
+        }
+
+    @Test
+    fun playErrorHaptics_onFailedLockscreenAuth_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            updateFingerprintAuthStatus(isSuccess = false)
+
+            assertThat(playErrorHaptic).isNotNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            verify(vibratorHelper)
+                .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
+            verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
+        }
+
+    @Test
+    fun skipsSuccessHaptics_whenPowerButtonDown_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playSuccessHaptic by
+                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            allowHapticsOnSfps(isPowerButtonDown = true)
+            unlockWithFingerprintAuth()
+
+            assertThat(playSuccessHaptic).isNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            verify(vibratorHelper, never())
+                .vibrateAuthSuccess(
+                    "SceneContainerStartable, $currentSceneKey device-entry::success"
+                )
+            verify(vibratorHelper, never()).vibrateAuthError(anyString())
+
+            updateFingerprintAuthStatus(isSuccess = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun skipsSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playSuccessHaptic by
+                collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            allowHapticsOnSfps(lastPowerPress = 50)
+            unlockWithFingerprintAuth()
+
+            assertThat(playSuccessHaptic).isNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            verify(vibratorHelper, never())
+                .vibrateAuthSuccess(
+                    "SceneContainerStartable, $currentSceneKey device-entry::success"
+                )
+            verify(vibratorHelper, never()).vibrateAuthError(anyString())
+
+            updateFingerprintAuthStatus(isSuccess = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Gone)
+        }
+
+    @Test
+    fun skipsErrorHaptics_whenPowerButtonDown_sfps() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
+
+            setupBiometricAuth(hasSfps = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
+            updateFingerprintAuthStatus(isSuccess = false)
+
+            assertThat(playErrorHaptic).isNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            verify(vibratorHelper, never())
+                .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
+            verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
+        }
+
+    @Test
+    fun skipsFaceErrorHaptics_nonSfps_coEx() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+            val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic)
+
+            setupBiometricAuth(hasUdfps = true, hasFace = true)
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse()
+
+            underTest.start()
+            updateFaceAuthStatus(isSuccess = false)
+
+            assertThat(playErrorHaptic).isNull()
+            assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+            verify(vibratorHelper, never())
+                .vibrateAuthError("SceneContainerStartable, $currentSceneKey device-entry::error")
+            verify(vibratorHelper, never()).vibrateAuthSuccess(anyString())
+        }
+
+    @Test
     fun hydrateSystemUiState() =
         testScope.runTest {
             val transitionStateFlow = prepareState()
@@ -1194,41 +1400,6 @@
         }
 
     @Test
-    fun hydrateWindowController_setBouncerShowing() =
-        testScope.runTest {
-            underTest.start()
-            val notificationShadeWindowController = kosmos.notificationShadeWindowController
-            val transitionStateFlow = prepareState(initialSceneKey = Scenes.Lockscreen)
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-            verify(notificationShadeWindowController, never()).setBouncerShowing(true)
-            verify(notificationShadeWindowController, times(1)).setBouncerShowing(false)
-
-            emulateSceneTransition(transitionStateFlow, Scenes.Bouncer)
-            verify(notificationShadeWindowController, times(1)).setBouncerShowing(true)
-            verify(notificationShadeWindowController, times(1)).setBouncerShowing(false)
-
-            emulateSceneTransition(transitionStateFlow, Scenes.Lockscreen)
-            verify(notificationShadeWindowController, times(1)).setBouncerShowing(true)
-            verify(notificationShadeWindowController, times(2)).setBouncerShowing(false)
-
-            kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                SuccessFingerprintAuthenticationStatus(0, true)
-            )
-            assertThat(currentScene).isEqualTo(Scenes.Gone)
-            verify(notificationShadeWindowController, times(1)).setBouncerShowing(true)
-            verify(notificationShadeWindowController, times(2)).setBouncerShowing(false)
-
-            emulateSceneTransition(transitionStateFlow, Scenes.Lockscreen)
-            verify(notificationShadeWindowController, times(1)).setBouncerShowing(true)
-            verify(notificationShadeWindowController, times(2)).setBouncerShowing(false)
-
-            emulateSceneTransition(transitionStateFlow, Scenes.Bouncer)
-            verify(notificationShadeWindowController, times(2)).setBouncerShowing(true)
-            verify(notificationShadeWindowController, times(2)).setBouncerShowing(false)
-        }
-
-    @Test
     fun hydrateWindowController_setKeyguardOccluded() =
         testScope.runTest {
             underTest.start()
@@ -1682,6 +1853,7 @@
             underTest.start()
 
             // run all pending dismiss succeeded/cancelled calls from setup:
+            runCurrent()
             kosmos.fakeExecutor.runAllReady()
 
             val dismissCallback: IKeyguardDismissCallback = mock()
@@ -1877,4 +2049,92 @@
         FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
             this.isPinned.value = isPinned
         }
+
+    private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
+        kosmos.fingerprintPropertyRepository.setProperties(
+            sensorId = 0,
+            strength = SensorStrength.STRONG,
+            sensorType = fingerprintSensorType,
+            sensorLocations = mapOf(),
+        )
+        kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+    }
+
+    private fun setFaceEnrolled() {
+        kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+    }
+
+    private fun TestScope.allowHapticsOnSfps(
+        isPowerButtonDown: Boolean = false,
+        lastPowerPress: Long = 10000
+    ) {
+        kosmos.fakeKeyEventRepository.setPowerButtonDown(isPowerButtonDown)
+
+        kosmos.powerRepository.updateWakefulness(
+            WakefulnessState.AWAKE,
+            WakeSleepReason.POWER_BUTTON,
+            WakeSleepReason.POWER_BUTTON,
+            powerButtonLaunchGestureTriggered = false,
+        )
+
+        advanceTimeBy(lastPowerPress)
+        runCurrent()
+    }
+
+    private fun unlockWithFingerprintAuth() {
+        kosmos.fakeKeyguardRepository.setBiometricUnlockSource(
+            BiometricUnlockSource.FINGERPRINT_SENSOR
+        )
+        kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.UNLOCK_COLLAPSING)
+    }
+
+    private fun TestScope.setupBiometricAuth(
+        hasSfps: Boolean = false,
+        hasUdfps: Boolean = false,
+        hasFace: Boolean = false
+    ) {
+        if (hasSfps) {
+            setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON)
+        }
+
+        if (hasUdfps) {
+            setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+        }
+
+        if (hasFace) {
+            setFaceEnrolled()
+        }
+
+        prepareState(
+            authenticationMethod = AuthenticationMethodModel.Pin,
+            isDeviceUnlocked = false,
+            initialSceneKey = Scenes.Lockscreen,
+        )
+    }
+
+    private fun updateFingerprintAuthStatus(isSuccess: Boolean) {
+        if (isSuccess) {
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+        } else {
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                FailFingerprintAuthenticationStatus
+            )
+        }
+    }
+
+    private fun updateFaceAuthStatus(isSuccess: Boolean) {
+        if (isSuccess) {
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+                SuccessFaceAuthenticationStatus(
+                    successResult = Mockito.mock(FaceManager.AuthenticationResult::class.java)
+                )
+            )
+        } else {
+            kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
+                FailedFaceAuthenticationStatus()
+            )
+        }
+    }
 }
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/GoneSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
similarity index 83%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
index b526275..03106ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModelTest.kt
@@ -43,17 +43,17 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
-class GoneSceneActionsViewModelTest : SysuiTestCase() {
+class GoneUserActionsViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val shadeRepository by lazy { kosmos.shadeRepository }
-    private lateinit var underTest: GoneSceneActionsViewModel
+    private lateinit var underTest: GoneUserActionsViewModel
 
     @Before
     fun setUp() {
         underTest =
-            GoneSceneActionsViewModel(
+            GoneUserActionsViewModel(
                 shadeInteractor = kosmos.shadeInteractor,
             )
         underTest.activateIn(testScope)
@@ -62,21 +62,21 @@
     @Test
     fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.actions)
+            val userActions by collectLastValue(underTest.actions)
             shadeRepository.setShadeLayoutWide(true)
             runCurrent()
 
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey)
+            assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey)
                 .isEqualTo(ToSplitShade)
         }
 
     @Test
     fun downTransitionKey_splitShadeDisabled_isNull() =
         testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.actions)
+            val userActions by collectLastValue(underTest.actions)
             shadeRepository.setShadeLayoutWide(false)
             runCurrent()
 
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
+            assertThat(userActions?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull()
         }
 }
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 b315783..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,7 @@
 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
 import com.android.systemui.testKosmos
@@ -48,6 +48,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @EnableSceneContainer
@@ -72,6 +73,7 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                logger = kosmos.sceneLogger,
                 motionEventHandlerReceiver = { motionEventHandler ->
                     this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
                 },
@@ -111,11 +113,6 @@
         }
 
     @Test
-    fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys).isEqualTo(kosmos.sceneKeys)
-    }
-
-    @Test
     fun sceneTransition() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
@@ -235,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/scene/ui/viewmodel/SceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt
similarity index 89%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModelTest.kt
index 206d3ac..972afb5 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/UserActionsViewModelTest.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
@@ -43,19 +42,18 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class SceneActionsViewModelTest : SysuiTestCase() {
+class UserActionsViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    private val underTest = FakeSceneActionsViewModel()
+    private val underTest = FakeUserActionsViewModel()
 
     @Test
     fun actions_emptyBeforeActivation() =
         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,18 +112,17 @@
 
             job.cancel()
             runCurrent()
-            assertThat(underTest.isActive).isFalse()
             assertThat(actions).isEmpty()
         }
 
-    private class FakeSceneActionsViewModel : SceneActionsViewModel() {
+    private class FakeUserActionsViewModel : UserActionsViewModel() {
 
         val upstream = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
 
         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/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/NotificationShadeWindowModelTest.kt
index 6a88664..f5022b9 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
@@ -16,16 +16,28 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
 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.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.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -83,4 +95,155 @@
             )
             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()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun withSceneContainer_bouncerShowing_providesTheCorrectState() =
+        testScope.runTest {
+            val bouncerShowing by collectLastValue(underTest.isBouncerShowing)
+
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(Scenes.Lockscreen)
+                )
+            kosmos.sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+            assertThat(bouncerShowing).isFalse()
+
+            transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer)
+            runCurrent()
+            assertThat(bouncerShowing).isTrue()
+        }
+
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER)
+    fun withComposeBouncer_bouncerShowing_providesTheCorrectState() =
+        testScope.runTest {
+            val bouncerShowing by collectLastValue(underTest.isBouncerShowing)
+
+            kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = false)
+            runCurrent()
+            assertThat(bouncerShowing).isFalse()
+
+            kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true)
+            runCurrent()
+            assertThat(bouncerShowing).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun withSceneContainer_doesBouncerRequireIme_providesTheCorrectState() =
+        testScope.runTest {
+            val bouncerRequiresIme by collectLastValue(underTest.doesBouncerRequireIme)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(Scenes.Bouncer)
+                )
+            kosmos.sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+            assertThat(bouncerRequiresIme).isFalse()
+
+            // go back to lockscreen
+            transitionState.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
+            runCurrent()
+
+            // change auth method
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
+            // go back to bouncer
+            transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer)
+            runCurrent()
+            assertThat(bouncerRequiresIme).isTrue()
+        }
+
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_COMPOSE_BOUNCER)
+    fun withComposeBouncer_doesBouncerRequireIme_providesTheCorrectState() =
+        testScope.runTest {
+            val bouncerRequiresIme by collectLastValue(underTest.doesBouncerRequireIme)
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pin
+            )
+
+            kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true)
+            runCurrent()
+            assertThat(bouncerRequiresIme).isFalse()
+
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
+            kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(isShowing = true)
+            runCurrent()
+            assertThat(bouncerRequiresIme).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt
deleted file mode 100644
index 3f087b4..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt
+++ /dev/null
@@ -1,170 +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.shade.ui.viewmodel
-
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
-import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.lifecycle.activateIn
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.testKosmos
-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)
-@TestableLooper.RunWithLooper
-@EnableSceneContainer
-class OverlayShadeViewModelTest : SysuiTestCase() {
-
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val sceneInteractor = kosmos.sceneInteractor
-    private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
-
-    private val underTest = kosmos.overlayShadeViewModel
-
-    @Before
-    fun setUp() {
-        underTest.activateIn(testScope)
-    }
-
-    @Test
-    fun backgroundScene_deviceLocked_lockscreen() =
-        testScope.runTest {
-            val backgroundScene by collectLastValue(underTest.backgroundScene)
-
-            lockDevice()
-
-            assertThat(backgroundScene).isEqualTo(Scenes.Lockscreen)
-        }
-
-    @Test
-    fun backgroundScene_deviceUnlocked_gone() =
-        testScope.runTest {
-            val backgroundScene by collectLastValue(underTest.backgroundScene)
-
-            lockDevice()
-            unlockDevice()
-
-            assertThat(backgroundScene).isEqualTo(Scenes.Gone)
-        }
-
-    @Test
-    fun backgroundScene_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
-        testScope.runTest {
-            val backgroundScene by collectLastValue(underTest.backgroundScene)
-            val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
-
-            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.None
-            )
-            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
-            sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-            runCurrent()
-
-            assertThat(backgroundScene).isEqualTo(Scenes.Lockscreen)
-        }
-
-    @Test
-    fun backgroundScene_authMethodSwipe_lockscreenDismissed_goesToGone() =
-        testScope.runTest {
-            val backgroundScene by collectLastValue(underTest.backgroundScene)
-            val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
-
-            kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
-            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.None
-            )
-            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
-            sceneInteractor.changeScene(Scenes.Gone, "reason")
-            runCurrent()
-
-            assertThat(backgroundScene).isEqualTo(Scenes.Gone)
-        }
-
-    @Test
-    fun onScrimClicked_onLockscreen_goesToLockscreen() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            lockDevice()
-            sceneInteractor.changeScene(Scenes.Bouncer, "reason")
-            runCurrent()
-            assertThat(currentScene).isNotEqualTo(Scenes.Lockscreen)
-
-            underTest.onScrimClicked()
-
-            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-        }
-
-    @Test
-    fun onScrimClicked_deviceWasEntered_goesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            val backgroundScene by collectLastValue(underTest.backgroundScene)
-
-            lockDevice()
-            unlockDevice()
-            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
-            runCurrent()
-            assertThat(backgroundScene).isEqualTo(Scenes.Gone)
-            assertThat(currentScene).isNotEqualTo(Scenes.Gone)
-
-            underTest.onScrimClicked()
-
-            assertThat(currentScene).isEqualTo(Scenes.Gone)
-        }
-
-    private fun TestScope.lockDevice() {
-        val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
-
-        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-        assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
-        sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-        runCurrent()
-    }
-
-    private fun TestScope.unlockDevice() {
-        val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus)
-
-        kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-            SuccessFingerprintAuthenticationStatus(0, true)
-        )
-        assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
-        sceneInteractor.changeScene(Scenes.Gone, "reason")
-        runCurrent()
-    }
-}
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/ShadeUserActionsViewModelTest.kt
similarity index 86%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
index 06a02c6..9f3e126e 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/ShadeUserActionsViewModelTest.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
@@ -63,7 +64,7 @@
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
 @DisableFlags(DualShade.FLAG_NAME)
-class ShadeSceneActionsViewModelTest : SysuiTestCase() {
+class ShadeUserActionsViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
@@ -71,7 +72,7 @@
     private val shadeRepository by lazy { kosmos.shadeRepository }
     private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter }
 
-    private val underTest: ShadeSceneActionsViewModel by lazy { kosmos.shadeSceneActionsViewModel }
+    private val underTest: ShadeUserActionsViewModel by lazy { kosmos.shadeUserActionsViewModel }
 
     @Before
     fun setUp() {
@@ -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/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index eac86e5..ce9b3be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -23,7 +23,6 @@
 import android.os.Handler
 import android.testing.TestableLooper
 import android.view.View
-import android.widget.FrameLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -46,6 +45,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.never
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -67,12 +68,9 @@
 
     @Mock private lateinit var session: SmartspaceSession
 
-    private lateinit var controller: CommunalSmartspaceController
+    private val preconditionListenerCaptor = argumentCaptor<SmartspacePrecondition.Listener>()
 
-    // TODO(b/272811280): Remove usage of real view
-    private val fakeParent by lazy {
-        FrameLayout(context)
-    }
+    private lateinit var controller: CommunalSmartspaceController
 
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -155,6 +153,26 @@
         verify(session).close()
     }
 
+    /** Ensures smartspace session begins when precondition is met if there is any listener. */
+    @Test
+    fun testConnectOnPreconditionMet() {
+        // Precondition not met
+        `when`(precondition.conditionsMet()).thenReturn(false)
+        controller.addListener(listener)
+
+        // Verify session not created because precondition not met
+        verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+        // Precondition met
+        `when`(precondition.conditionsMet()).thenReturn(true)
+        verify(precondition).addListener(preconditionListenerCaptor.capture())
+        val preconditionListener = preconditionListenerCaptor.firstValue
+        preconditionListener.onCriteriaChanged()
+
+        // Verify session created
+        verify(smartspaceManager).createSmartspaceSession(any())
+    }
+
     /**
      * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
      * view is detached.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 75ecb2c..beba162 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -133,7 +133,8 @@
                 mVisibilityLocationProvider,
                 mVisualStabilityProvider,
                 mWakefulnessLifecycle,
-                mKosmos.getCommunalInteractor(),
+                mKosmos.getCommunalSceneInteractor(),
+                mKosmos.getShadeInteractor(),
                 mKosmos.getKeyguardTransitionInteractor(),
                 mLogger);
         mCoordinator.attach(mNotifPipeline);
@@ -561,11 +562,12 @@
 
     @Test
     public void testCommunalShowingWillNotSuppressReordering() {
-        // GIVEN panel is expanded and communal is showing
+        // GIVEN panel is expanded, communal is showing, and QS is collapsed
         setPulsing(false);
         setFullyDozed(false);
         setSleepy(false);
         setPanelExpanded(true);
+        setQsExpanded(false);
         setCommunalShowing(true);
 
         // Reordering should be allowed
@@ -573,6 +575,20 @@
     }
 
     @Test
+    public void testQsExpandedOverCommunalWillSuppressReordering() {
+        // GIVEN panel is expanded and communal is showing, but QS is expanded
+        setPulsing(false);
+        setFullyDozed(false);
+        setSleepy(false);
+        setPanelExpanded(true);
+        setQsExpanded(true);
+        setCommunalShowing(true);
+
+        // Reordering should not be allowed
+        assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry));
+    }
+
+    @Test
     public void testQueryingEntryReorderingButNotReportingReorderSuppressedDoesNotInvalidate() {
         // GIVEN visual stability is being maintained b/c panel is expanded
         setPulsing(false);
@@ -631,7 +647,12 @@
                         new ObservableTransitionState.Idle(
                                 isShowing ? CommunalScenes.Communal : CommunalScenes.Blank)
                 );
-        mKosmos.getCommunalRepository().setTransitionState(showingFlow);
+        mKosmos.getCommunalSceneInteractor().setTransitionState(showingFlow);
+        mTestScope.getTestScheduler().runCurrent();
+    }
+
+    private void setQsExpanded(boolean isExpanded) {
+        mKosmos.getShadeRepository().setQsExpansion(isExpanded ? 1.0f : 0.0f);
         mTestScope.getTestScheduler().runCurrent();
     }
 
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/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index a6fdd03..b5dbc3f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -22,8 +22,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
@@ -73,11 +71,6 @@
     private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
 
     private val mainExecutor = FakeExecutor(FakeSystemClock())
-    private val featureFlags =
-        FakeFeatureFlagsClassic().also {
-            it.set(Flags.INSTANT_TETHER, true)
-            it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
-        }
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -93,7 +86,6 @@
 
         realImpl =
             WifiRepositoryImpl(
-                featureFlags,
                 testScope.backgroundScope,
                 mainExecutor,
                 testDispatcher,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 1237347..dc24cf7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -25,14 +25,14 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiIcons
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.TestScope
@@ -58,10 +59,11 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class WifiViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     private lateinit var underTest: WifiViewModel
 
-    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+    private val tableLogBuffer = logcatTableLogBuffer(kosmos, "WifiViewModelTest")
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -86,7 +88,7 @@
                 AirplaneModeInteractor(
                     airplaneModeRepository,
                     connectivityRepository,
-                    FakeMobileConnectionsRepository(),
+                    kosmos.fakeMobileConnectionsRepository,
                 ),
                 tableLogBuffer,
                 testScope.backgroundScope,
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/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 11504aa..fb32855 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -16,13 +16,16 @@
 
 package com.android.systemui.statusbar.policy.domain.interactor
 
+import android.app.AutomaticZenRule
 import android.app.NotificationManager.Policy
 import android.provider.Settings
 import android.provider.Settings.Secure.ZEN_DURATION
 import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
 import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
+import android.service.notification.SystemZenRules
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.R
 import com.android.settingslib.notification.data.repository.updateNotificationPolicy
 import com.android.settingslib.notification.modes.TestModeBuilder
 import com.android.systemui.SysuiTestCase
@@ -217,4 +220,121 @@
             assertThat(zenModeRepository.getModeActiveDuration(manualDnd.id))
                 .isEqualTo(Duration.ofMinutes(60))
         }
+
+    @Test
+    fun activeModes_computesMainActiveMode() =
+        testScope.runTest {
+            val activeModes by collectLastValue(underTest.activeModes)
+
+            zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
+            zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
+
+            runCurrent()
+            assertThat(activeModes?.modeNames).hasSize(0)
+            assertThat(activeModes?.mainMode).isNull()
+
+            zenModeRepository.activateMode("Other")
+            runCurrent()
+            assertThat(activeModes?.modeNames).containsExactly("Mode Other")
+            assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Other")
+
+            zenModeRepository.activateMode("Bedtime")
+            runCurrent()
+            assertThat(activeModes?.modeNames)
+                .containsExactly("Mode Bedtime", "Mode Other")
+                .inOrder()
+            assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
+
+            zenModeRepository.deactivateMode("Other")
+            runCurrent()
+            assertThat(activeModes?.modeNames).containsExactly("Mode Bedtime")
+            assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
+
+            zenModeRepository.deactivateMode("Bedtime")
+            runCurrent()
+            assertThat(activeModes?.modeNames).hasSize(0)
+            assertThat(activeModes?.mainMode).isNull()
+        }
+
+    @Test
+    fun getActiveModes_computesMainActiveMode() = runTest {
+        zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
+        zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
+
+        var activeModes = underTest.getActiveModes()
+        assertThat(activeModes.modeNames).hasSize(0)
+        assertThat(activeModes.mainMode).isNull()
+
+        zenModeRepository.activateMode("Other")
+        activeModes = underTest.getActiveModes()
+        assertThat(activeModes.modeNames).containsExactly("Mode Other")
+        assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other")
+
+        zenModeRepository.activateMode("Bedtime")
+        activeModes = underTest.getActiveModes()
+        assertThat(activeModes.modeNames).containsExactly("Mode Bedtime", "Mode Other").inOrder()
+        assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+
+        zenModeRepository.deactivateMode("Other")
+        activeModes = underTest.getActiveModes()
+        assertThat(activeModes.modeNames).containsExactly("Mode Bedtime")
+        assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+
+        zenModeRepository.deactivateMode("Bedtime")
+        activeModes = underTest.getActiveModes()
+        assertThat(activeModes.modeNames).hasSize(0)
+        assertThat(activeModes.mainMode).isNull()
+    }
+
+    @Test
+    fun mainActiveMode_flows() =
+        testScope.runTest {
+            val mainActiveMode by collectLastValue(underTest.mainActiveMode)
+
+            zenModeRepository.addModes(
+                listOf(
+                    TestModeBuilder()
+                        .setId("Bedtime")
+                        .setName("Mode Bedtime")
+                        .setType(AutomaticZenRule.TYPE_BEDTIME)
+                        .setActive(false)
+                        .setPackage(mContext.packageName)
+                        .setIconResId(R.drawable.ic_zen_mode_type_bedtime)
+                        .build(),
+                    TestModeBuilder()
+                        .setId("Other")
+                        .setName("Mode Other")
+                        .setType(AutomaticZenRule.TYPE_OTHER)
+                        .setActive(false)
+                        .setPackage(SystemZenRules.PACKAGE_ANDROID)
+                        .setIconResId(R.drawable.ic_zen_mode_type_other)
+                        .build(),
+                )
+            )
+
+            runCurrent()
+            assertThat(mainActiveMode).isNull()
+
+            zenModeRepository.activateMode("Other")
+            runCurrent()
+            assertThat(mainActiveMode?.name).isEqualTo("Mode Other")
+            assertThat(mainActiveMode?.icon?.key?.resId)
+                .isEqualTo(R.drawable.ic_zen_mode_type_other)
+
+            zenModeRepository.activateMode("Bedtime")
+            runCurrent()
+            assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
+            assertThat(mainActiveMode?.icon?.key?.resId)
+                .isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
+
+            zenModeRepository.deactivateMode("Other")
+            runCurrent()
+            assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
+            assertThat(mainActiveMode?.icon?.key?.resId)
+                .isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
+
+            zenModeRepository.deactivateMode("Bedtime")
+            runCurrent()
+            assertThat(mainActiveMode).isNull()
+        }
 }
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..a0f6431 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
@@ -330,6 +330,83 @@
         }
 
     @Test
+    fun tiles_populatesFieldsForAccessibility() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles)
+
+            repository.addModes(
+                listOf(
+                    TestModeBuilder()
+                        .setName("With description, inactive")
+                        .setManualInvocationAllowed(true)
+                        .setTriggerDescription("When the going gets tough")
+                        .setActive(false)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("With description, active")
+                        .setManualInvocationAllowed(true)
+                        .setTriggerDescription("When in Rome")
+                        .setActive(true)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("With description, needs setup")
+                        .setManualInvocationAllowed(true)
+                        .setTriggerDescription("When you find yourself in a hole")
+                        .setEnabled(false, /* byUser= */ false)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Without description, inactive")
+                        .setManualInvocationAllowed(true)
+                        .setTriggerDescription(null)
+                        .setActive(false)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Without description, active")
+                        .setManualInvocationAllowed(true)
+                        .setTriggerDescription(null)
+                        .setActive(true)
+                        .build(),
+                    TestModeBuilder()
+                        .setName("Without description, needs setup")
+                        .setManualInvocationAllowed(true)
+                        .setTriggerDescription(null)
+                        .setEnabled(false, /* byUser= */ false)
+                        .build(),
+                )
+            )
+            runCurrent()
+
+            assertThat(tiles!!).hasSize(6)
+            with(tiles?.elementAt(0)!!) {
+                assertThat(this.stateDescription).isEqualTo("Off")
+                assertThat(this.subtextDescription).isEqualTo("When the going gets tough")
+            }
+            with(tiles?.elementAt(1)!!) {
+                assertThat(this.stateDescription).isEqualTo("On")
+                assertThat(this.subtextDescription).isEqualTo("When in Rome")
+            }
+            with(tiles?.elementAt(2)!!) {
+                assertThat(this.stateDescription).isEqualTo("Off")
+                assertThat(this.subtextDescription).isEqualTo("Set up")
+            }
+            with(tiles?.elementAt(3)!!) {
+                assertThat(this.stateDescription).isEqualTo("Off")
+                assertThat(this.subtextDescription).isEmpty()
+            }
+            with(tiles?.elementAt(4)!!) {
+                assertThat(this.stateDescription).isEqualTo("On")
+                assertThat(this.subtextDescription).isEmpty()
+            }
+            with(tiles?.elementAt(5)!!) {
+                assertThat(this.stateDescription).isEqualTo("Off")
+                assertThat(this.subtextDescription).isEqualTo("Set up")
+            }
+
+            // All tiles have the same long click info
+            tiles!!.forEach { assertThat(it.onLongClickLabel).isEqualTo("Open settings") }
+        }
+
+    @Test
     fun onClick_togglesTileState() =
         testScope.runTest {
             val tiles by collectLastValue(underTest.tiles)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
index 0f56d0b..fa7f37c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
@@ -90,8 +90,9 @@
                 assertThat(model)
                     .isEqualTo(
                         MediaOutputComponentModel.Calling(
-                            AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon),
-                            false,
+                            device = AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon),
+                            isInAudioSharing = false,
+                            canOpenAudioSwitcher = false,
                         )
                     )
             }
@@ -101,6 +102,9 @@
     fun hasSession_stateIs_MediaSession() =
         with(kosmos) {
             testScope.runTest {
+                localMediaRepository.updateCurrentConnectedDevice(
+                    TestMediaDevicesFactory.builtInMediaDevice()
+                )
                 mediaControllerRepository.setActiveSessions(listOf(localMediaController))
 
                 val model by collectLastValue(underTest.mediaOutputModel.filterData())
@@ -113,6 +117,7 @@
                     assertThat(device)
                         .isEqualTo(AudioOutputDevice.BuiltIn("built_in_media", testIcon))
                     assertThat(isInAudioSharing).isFalse()
+                    assertThat(canOpenAudioSwitcher).isTrue()
                 }
             }
         }
@@ -129,8 +134,9 @@
                 assertThat(model)
                     .isEqualTo(
                         MediaOutputComponentModel.Idle(
-                            AudioOutputDevice.BuiltIn("built_in_media", testIcon),
-                            true,
+                            device = AudioOutputDevice.BuiltIn("built_in_media", testIcon),
+                            isInAudioSharing = true,
+                            canOpenAudioSwitcher = false,
                         )
                     )
             }
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/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt
index 509f022..84f39af 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt
@@ -21,4 +21,6 @@
 interface BcSmartspaceConfigPlugin {
     /** Gets default date/weather disabled status. */
     val isDefaultDateWeatherDisabled: Boolean
+    /** Gets if Smartspace should use ViewPager2 */
+    val isViewPager2Enabled: Boolean
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
index 789a473..f920b18 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
@@ -4,6 +4,7 @@
 import android.util.Log
 import android.view.View
 import androidx.annotation.VisibleForTesting
+import androidx.core.text.util.LocalePreferences
 
 typealias WeatherTouchAction = (View) -> Unit
 
@@ -54,12 +55,35 @@
             }
         }
 
-        private fun readIntFromBundle(extras: Bundle, key: String): Int? =
+        private fun readIntFromBundle(extras: Bundle, key: String): Int? {
             try {
-                extras.getString(key)?.toInt()
+                return extras.getString(key)?.toInt()
             } catch (e: Exception) {
-                null
+                return null
             }
+        }
+
+        fun getPlaceholderWeatherData(): WeatherData {
+            return getPlaceholderWeatherData(
+                LocalePreferences.getTemperatureUnit() == LocalePreferences.TemperatureUnit.CELSIUS
+            )
+        }
+
+        private const val DESCRIPTION_PLACEHODLER = ""
+        private const val TEMPERATURE_FAHRENHEIT_PLACEHOLDER = 58
+        private const val TEMPERATURE_CELSIUS_PLACEHOLDER = 21
+        private val WEATHERICON_PLACEHOLDER = WeatherData.WeatherStateIcon.MOSTLY_SUNNY
+
+        fun getPlaceholderWeatherData(useCelsius: Boolean): WeatherData {
+            return WeatherData(
+                description = DESCRIPTION_PLACEHODLER,
+                state = WEATHERICON_PLACEHOLDER,
+                temperature =
+                    if (useCelsius) TEMPERATURE_CELSIUS_PLACEHOLDER
+                    else TEMPERATURE_FAHRENHEIT_PLACEHOLDER,
+                useCelsius = useCelsius,
+            )
+        }
     }
 
     // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
diff --git a/packages/SystemUI/res/drawable/arrow_pointing_down.xml b/packages/SystemUI/res/drawable/arrow_pointing_down.xml
index be39683..ca573c7 100644
--- a/packages/SystemUI/res/drawable/arrow_pointing_down.xml
+++ b/packages/SystemUI/res/drawable/arrow_pointing_down.xml
@@ -19,7 +19,7 @@
     android:height="24dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
-    android:tint="?attr/colorControlNormal">
+    android:tint="?android:attr/textColorPrimary">
     <path
         android:fillColor="@android:color/white"
         android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17" />
diff --git a/packages/SystemUI/res/drawable/brightness_bar.xml b/packages/SystemUI/res/drawable/brightness_bar.xml
index 2afe164..3d1c1fb 100644
--- a/packages/SystemUI/res/drawable/brightness_bar.xml
+++ b/packages/SystemUI/res/drawable/brightness_bar.xml
@@ -21,7 +21,7 @@
         android:viewportHeight="48">
 <path
     android:pathData="M2,22L302,22A2,2 0,0 1,304 24L304,24A2,2 0,0 1,302 26L2,26A2,2 0,0 1,0 24L0,24A2,2 0,0 1,2 22z"
-    android:fillColor="@color/brightness_slider_track"/>
+    android:fillColor="?androidprv:attr/customColorShadeInactive"/>
 <path
     android:pathData="M24,0L205.71,0A24,24 0,0 1,229.71 24L229.71,24A24,24 0,0 1,205.71 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
     android:fillColor="?attr/shadeActive"/>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index cae9d6b..ec15b10 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -15,6 +15,7 @@
   ~ limitations under the License.
   -->
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
             android:paddingMode="stack" >
     <item android:id="@android:id/background"
         android:gravity="center_vertical|fill_horizontal">
@@ -24,7 +25,7 @@
             <shape>
                 <size android:height="@dimen/rounded_slider_track_width" />
                 <corners android:radius="@dimen/rounded_slider_track_corner_radius" />
-                <solid android:color="@color/brightness_slider_track" />
+                <solid android:color="?androidprv:attr/customColorShadeInactive" />
             </shape>
         </inset>
     </item>
diff --git a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
index e06bfdc..368fe82 100644
--- a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
@@ -52,7 +52,7 @@
         <Button
             android:id="@android:id/button1"
             style="?android:attr/buttonBarPositiveButtonStyle"
-            android:layout_marginStart="8dp"
+            android:layout_marginStart="@dimen/dialog_button_side_margin"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content" />
     </com.android.internal.widget.ButtonBarLayout>
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/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
index 154397d..690a89a 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -17,7 +17,6 @@
      the chip. -->
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/ongoing_activity_chip"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
     android:layout_gravity="center_vertical|start"
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index aa083ad..0533c7e 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -64,30 +64,27 @@
             android:layout_height="wrap_content"
             android:text="@string/screenrecord_permission_dialog_warning_entire_screen"
             style="@style/TextAppearance.Dialog.Body.Message"
-            android:gravity="start"/>
+            android:gravity="start"
+            android:textAlignment="gravity"/>
 
         <!-- Buttons -->
         <com.android.internal.widget.ButtonBarLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="horizontal"
-            android:layout_marginTop="@dimen/screenrecord_buttons_margin_top">
+            android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+            android:gravity="end">
             <Button
                 android:id="@android:id/button2"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_weight="0"
                 android:text="@string/cancel"
                 style="@style/Widget.Dialog.Button.BorderButton" />
-            <Space
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"/>
             <Button
                 android:id="@android:id/button1"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_weight="0"
+                android:layout_marginStart="@dimen/dialog_button_side_margin"
                 android:text="@string/screenrecord_continue"
                 style="@style/Widget.Dialog.Button" />
         </com.android.internal.widget.ButtonBarLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 4247c7e..ee75b31 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -99,7 +99,10 @@
                         android:gravity="center_vertical|start"
                     />
 
-                    <include layout="@layout/ongoing_activity_chip" />
+                    <include layout="@layout/ongoing_activity_chip"
+                        android:id="@+id/ongoing_activity_chip_primary"/>
+
+                    <!-- TODO(b/364653005): Add a second activity chip. -->
 
                     <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
                         android:id="@+id/notification_icon_area"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index dfdb15d..a1be54c 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gebruik Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Gekoppel"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Oudiodeling"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gestoor"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ontkoppel"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveer"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Noodoproepe of SOS"</string>
     <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..23f3766 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ብሉቱዝን ይጠቀሙ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ተገናኝቷል"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"የድምጽ ማጋራት"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ተቀምጧል"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ግንኙነትን አቋርጥ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ያግብሩ"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"የአደጋ ጥሪዎች ወይም ኤስኦኤስ"</string>
     <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..0ac0d03 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"استخدام البلوتوث"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متّصل"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"مشاركة الصوت"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"إلغاء الربط"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"تفعيل"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"مكالمات الطوارئ أو ميزة \"اتصالات طوارئ بالقمر الصناعي\""</string>
     <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 +848,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..a770c61 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যৱহাৰ কৰক"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"সংযুক্ত আছে"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"অডিঅ’ শ্বেয়াৰ কৰা"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ছেভ কৰা হৈছে"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"সংযোগ বিচ্ছিন্ন কৰক"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"সক্ৰিয় কৰক"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"জৰুৰীকালীন কল বা SOS"</string>
     <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..cd7bbaf 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-u açın"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Qoşulub"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio paylaşma"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Yadda saxlandı"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"əlaqəni kəsin"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivləşdirin"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Təcili zənglər və ya SOS"</string>
     <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..25ecce5 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Deljenje zvuka"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinite vezu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivirajte"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Hitni pozivi ili hitna pomoć"</string>
     <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..5ea0621 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Выкарыстоўваць Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Падключана"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Абагульванне аўдыя"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Захавана"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"адключыць"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"актываваць"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Экстранныя выклікі або экстраннае спадарожнікавае падключэнне"</string>
     <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..ee11863 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Използване на Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Установена е връзка"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Споделяне на звука"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Запазено"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекратяване на връзката"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активиране"</string>
@@ -308,8 +310,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,8 +391,8 @@
     <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="custom" msgid="3337456985275158299">"Персонализирано"</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>
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Режим за работа с една ръка"</string>
@@ -436,6 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Спешни обаждания или SOS"</string>
     <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..2536cb7 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ব্লুটুথ ব্যবহার করুন"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"কানেক্ট করা আছে"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"অডিও শেয়ারিং"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"সেভ করা আছে"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ডিসকানেক্ট করুন"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"চালু করুন"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"জরুরি কল বা SOS"</string>
     <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..43ae5c7 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Dijeljenje zvuka"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sačuvano"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekid veze"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
@@ -308,8 +310,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 +382,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 +437,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 +675,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Hitni pozivi ili pomoć"</string>
     <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..187e75e 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -110,7 +110,7 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vols gravar la pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grava una aplicació"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grava tota la pantalla"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quan graves tota la pantalla, es grava tot el que es mostra en pantalla. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos, i l\'àudio i el vídeo."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quan graves tota la pantalla, es grava tot el que es mostra en pantalla. Per aquest motiu, ves amb compte amb elements com les contrasenyes, les dades de pagament, els missatges, les fotos, i l\'àudio i el vídeo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quan graves una aplicació, es grava tot el que es mostra o es reprodueix en aquesta aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos, i l\'àudio i el vídeo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grava la pantalla"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"Tria una aplicació per gravar"</string>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utilitza el Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connectat"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartició d\'àudio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Desat"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconnecta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activa"</string>
@@ -308,8 +310,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 +437,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 +508,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Trucades d\'emergència o SOS"</string>
     <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..13e0377 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Používat Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Připojeno"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Sdílení zvuku"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uloženo"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojit"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovat"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Tísňová volání nebo SOS"</string>
     <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..4ba9fe9 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Brug Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Der er oprettet forbindelse"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Lyddeling"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gemt"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"afbryd forbindelse"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivér"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Nødopkald eller SOS-meldinger via satellit"</string>
     <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..7a8d06c 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth verwenden"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbunden"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audiofreigabe"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gespeichert"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Verknüpfung aufheben"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivieren"</string>
@@ -308,8 +310,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 +382,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 +437,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>
@@ -629,7 +631,7 @@
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Einstellungen"</string>
     <string name="volume_panel_captioning_title" msgid="5984936949147684357">"Automatische Untertitel"</string>
     <string name="csd_lowered_title" product="default" msgid="2464112924151691129">"Auf verträglichere Lautstärke eingestellt"</string>
-    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"Die Kopfhörerlautstärke war länger als empfohlen hoch eingestellt"</string>
+    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"Die Kopfhörer sind schon länger als empfohlen auf hohe Lautstärke eingestellt"</string>
     <string name="csd_500_system_lowered_text" product="default" msgid="7414943302186884124">"Die Kopfhörerlautstärke hat für diese Woche das Sicherheitslimit überschritten"</string>
     <string name="csd_button_keep_listening" product="default" msgid="4093794049149286784">"Weiterhören"</string>
     <string name="csd_button_lower_volume" product="default" msgid="5347210412376264579">"Leiser stellen"</string>
@@ -718,6 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Notrufe oder SOS"</string>
     <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..015d625 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Χρήση Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Συνδέθηκε"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Κοινή χρήση ήχου"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Αποθηκεύτηκε"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"αποσύνδεση"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ενεργοποίηση"</string>
@@ -308,8 +310,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 +382,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Κλήσεις έκτακτης ανάγκης ή SOS"</string>
     <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..bab9a50 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio sharing"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -308,8 +310,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 +437,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 +720,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 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..67ce124 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio Sharing"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -435,6 +437,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 +720,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..bab9a50 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio sharing"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -308,8 +310,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 +437,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 +720,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 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..bab9a50 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Use Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connected"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio sharing"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saved"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnect"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activate"</string>
@@ -308,8 +310,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 +437,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 +720,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 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..4317f86 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎Use Bluetooth‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‎‎‏‎‏‏‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎Connected‎‏‎‎‏‎"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‏‎‎Audio Sharing‎‏‎‎‏‎"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎Saved‎‏‎‎‏‎"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎disconnect‎‏‎‎‏‎"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎activate‎‏‎‎‏‎"</string>
@@ -435,6 +437,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 +720,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..b9cfbaa 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -110,7 +110,7 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"¿Quieres grabar la pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grabar una app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grabar toda la pantalla"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Cuando grabes toda la pantalla, se registrará todo lo que se muestre en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Cuando grabes toda la pantalla, se grabará todo lo que se muestre en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Cuando grabes una app, se registrará todo lo que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grabar pantalla"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"Elige una app para grabar"</string>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Uso compartido de audio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Llamadas de emergencia o SOS"</string>
     <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..b392959 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartir audio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
@@ -308,8 +310,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 +382,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">"Config. personalizada"</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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Llamadas de emergencia o SOS"</string>
     <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..c56d101 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Kasuta Bluetoothi"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ühendatud"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Heli jagamine"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvestatud"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkesta ühendus"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiveeri"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Hädaabikõned või SOS"</string>
     <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..ccebe2b 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Erabili Bluetootha"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Konektatuta"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audioa partekatzea"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gordeta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deskonektatu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktibatu"</string>
@@ -308,8 +310,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 +382,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 +393,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Larrialdi-deiak edo SOS komunikazioa"</string>
     <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..7e622cf 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"استفاده از بلوتوث"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"متصل"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"اشتراک صدا"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ذخیره‌شده"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"قطع اتصال"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کردن"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"تماس اضطراری یا درخواست کمک اضطراری"</string>
     <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..4d7f41a3 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Käytä Bluetoothia"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Yhdistetty"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audionjako"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Tallennettu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"katkaise yhteys"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivoi"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Hätäpuhelut tai Satellite SOS"</string>
     <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..51dc60f 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Partage audio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"Déconnecter"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"Activer"</string>
@@ -308,8 +310,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 +382,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 +391,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Appels d\'urgence ou SOS"</string>
     <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 +1273,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..bd6c664 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -110,7 +110,7 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Enregistrer l\'écran ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Enregistrer une appli"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Enregistrer tout l\'écran"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Lorsque vous enregistrez tout votre écran, tout ce qui s\'affiche sur celui-ci est enregistré. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages, photos et contenus audio et vidéo."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Lorsque vous enregistrez l\'intégralité de votre écran, tout ce qui s\'y affiche est enregistré. Faites donc attention aux éléments tels que les mots de passe, les détails du mode de paiement, les messages, les photos, et les contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Lorsque vous enregistrez une appli, tout ce qui est affiché ou lu dans celle-ci est enregistré. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages, photos et contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Enregistrer l\'écran"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"Choisir l\'appli à enregistrer"</string>
@@ -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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Connecté"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Partage audio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Enregistré"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"dissocier"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activer"</string>
@@ -308,8 +310,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 +382,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Appels d\'urgence ou SOS"</string>
     <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..81e96d6 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Estableceuse a conexión"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio compartido"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Gardouse"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string>
@@ -308,8 +310,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 +382,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 +392,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Chamadas de emerxencia ou SOS"</string>
     <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..ffa8554 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"બ્લૂટૂથનો ઉપયોગ કરો"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"કનેક્ટેડ છે"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ઑડિયો શેરિંગ"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"સાચવેલું"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ડિસ્કનેક્ટ કરો"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"સક્રિય કરો"</string>
@@ -308,8 +310,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 +379,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"ઇમર્જન્સી કૉલ અથવા SOS"</string>
     <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..b56b30c 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -110,7 +110,7 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"क्या आपको स्क्रीन रिकॉर्ड करनी है?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"एक ऐप्लिकेशन की रिकॉर्डिंग करें"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"पूरी स्क्रीन रिकॉर्ड करें"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"पूरी स्क्रीन रिकॉर्ड करते समय, स्क्रीन पर दिखने वाली हर चीज़ रिकॉर्ड की जाती है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"पूरी स्क्रीन रिकॉर्ड करते समय, स्क्रीन पर दिखने वाली हर चीज़ रिकॉर्ड की जाती है. इसलिए पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज,  डिवाइस पर चल रहे ऑडियो और वीडियो, और फ़ोटो जैसी चीज़ों को लेकर सावधानी बरतें."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"किसी ऐप्लिकेशन को रिकॉर्ड करने के दौरान, उस पर दिख रहा कॉन्टेंट या चल रहा मीडिया दूसरी स्क्रीन पर भी रिकॉर्ड होता है. इसलिए, रिकॉर्ड करते समय पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, ऑडियो, और वीडियो को लेकर सावधानी बरतें."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"स्क्रीन रिकॉर्ड करें"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"रिकॉर्ड करने के लिए ऐप्लिकेशन चुनें"</string>
@@ -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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लूटूथ इस्तेमाल करें"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट है"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ऑडियो शेयर करने की सुविधा"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव किया गया"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिसकनेक्ट करें"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"चालू करें"</string>
@@ -308,8 +310,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 +393,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 +437,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 +511,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>
@@ -628,8 +630,8 @@
     <string name="sound_settings" msgid="8874581353127418308">"आवाज़ और वाइब्रेशन"</string>
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"सेटिंग"</string>
     <string name="volume_panel_captioning_title" msgid="5984936949147684357">"लाइव कैप्शन"</string>
-    <string name="csd_lowered_title" product="default" msgid="2464112924151691129">"आवाज़ को कम करके, सुरक्षित लेवल पर सेट कर दिया गया है"</string>
-    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"हेडफ़ोन की आवाज़ सुझाए गए समय से देर तक ज़्यादा रही"</string>
+    <string name="csd_lowered_title" product="default" msgid="2464112924151691129">"आवाज़ को कम करके, सुरक्षित लेवल पर सेट किया गया"</string>
+    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"हेडफ़ोन की आवाज़ सुझाए गए समय के बाद भी ज़्यादा रही"</string>
     <string name="csd_500_system_lowered_text" product="default" msgid="7414943302186884124">"इस हफ़्ते के लिए हेडफ़ोन की आवाज़, सुझाई गई सीमा से ज़्यादा हो गई है"</string>
     <string name="csd_button_keep_listening" product="default" msgid="4093794049149286784">"सुनना जारी रखें"</string>
     <string name="csd_button_lower_volume" product="default" msgid="5347210412376264579">"आवाज़ कम करें"</string>
@@ -718,6 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"आपातकालीन कॉल या एसओएस"</string>
     <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..e1b2df2 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Zajedničko slušanje"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Spremljeno"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekini vezu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviraj"</string>
@@ -308,8 +310,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,16 +382,16 @@
     <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>
-    <string name="thermal" msgid="6758074791325414831">"Termalno"</string>
+    <string name="thermal" msgid="6758074791325414831">"Pregrijavanje"</string>
     <string name="custom" msgid="3337456985275158299">"Prilagođeno"</string>
     <string name="custom_trace_settings_dialog_title" msgid="2608570500144830554">"Postavke prilagođenog praćenja"</string>
     <string name="restore_default" msgid="5259420807486239755">"Vrati na zadano"</string>
@@ -436,6 +437,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>
@@ -629,7 +631,7 @@
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Postavke"</string>
     <string name="volume_panel_captioning_title" msgid="5984936949147684357">"Automatski titlovi"</string>
     <string name="csd_lowered_title" product="default" msgid="2464112924151691129">"Glasnoća je stišana na sigurniju razinu"</string>
-    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"Glasnoća u slušalicama pojačana je dulje nego što se preporučuje"</string>
+    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"Zvuk u slušalicama bio je preglasan dulje nego što se preporučuje"</string>
     <string name="csd_500_system_lowered_text" product="default" msgid="7414943302186884124">"Glasnoća slušalica premašila je sigurno ograničenje za ovaj tjedan"</string>
     <string name="csd_button_keep_listening" product="default" msgid="4093794049149286784">"Nastavi slušati"</string>
     <string name="csd_button_lower_volume" product="default" msgid="5347210412376264579">"Stišaj"</string>
@@ -718,6 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Hitni pozivi ili SOS"</string>
     <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..f4d34fcc 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth használata"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Csatlakozva"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Hang megosztása"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Mentve"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"leválasztás"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiválás"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Segélyhívás vagy SOS"</string>
     <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..3b7855b 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Միացնել Bluetooth-ը"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Միացված է"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Աուդիոյի փոխանցում"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Պահված է"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"անջատել"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ակտիվացնել"</string>
@@ -308,8 +310,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 +382,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 +391,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 +437,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 +667,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Շտապ կանչեր կամ SOS"</string>
     <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..3ddd9f11 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Terhubung"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Berbagi Audio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"berhenti hubungkan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
@@ -308,8 +310,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 +382,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Panggilan darurat atau SOS"</string>
     <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 +789,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..8c57ed1 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Nota Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tengt"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Hljóði deilt"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Vistað"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"aftengja"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"virkja"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Neyðarsímtöl eða SOS"</string>
     <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..559e529 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -19,7 +19,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="4811759950673118541">"UI sistema"</string>
+    <string name="app_label" msgid="4811759950673118541">"UI di sistema"</string>
     <string name="battery_low_title" msgid="5319680173344341779">"Attivare il risparmio energetico?"</string>
     <string name="battery_low_description" msgid="3282977755476423966">"Batteria rimanente: <xliff:g id="PERCENTAGE">%s</xliff:g>. Il risparmio energetico attiva il tema scuro, limita l\'attività in background e ritarda le notifiche."</string>
     <string name="battery_low_intro" msgid="5148725009653088790">"Il risparmio energetico attiva il tema scuro, limita l\'attività in background e ritarda le notifiche."</string>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usa il Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Dispositivo connesso"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Condivisione audio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Dispositivo salvato"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"disconnetti"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"attiva"</string>
@@ -308,8 +310,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 +386,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" msgid="3337456985275158299">"Personalizzata"</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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Chiamate di emergenza o SOS"</string>
     <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..907f846 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"‏שימוש ב-Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"מחובר"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"שיתוף אודיו"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"נשמר"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ניתוק"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"הפעלה"</string>
@@ -308,8 +310,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>
@@ -382,7 +383,7 @@
     <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_start" msgid="2979831312582567056">"התחלה"</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_dropdown_header" msgid="5995983175678658329">"איזה חלק בחוויית השימוש שלך במכשיר הושפע?"</string>
@@ -436,6 +437,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 +720,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..54ba7b5 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth を使用"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"接続しました"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音声の共有"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存済み"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"接続を解除"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"有効化"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"緊急通報または SOS"</string>
     <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 +1318,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..bc0fa35 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ის გამოყენება"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"დაკავშირებული"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"აუდიოს გაზიარება"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"შენახული"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"კავშირის გაწყვეტა"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"გააქტიურება"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"გადაუდებელი ზარი ან SOS"</string>
     <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..aa06784 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-ты пайдалану"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Қосылды"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Аудио бөлісу"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сақталды"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажырату"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"іске қосу"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Құтқару қызметіне қоңырау шалу немесе SOS сигналын жіберу"</string>
     <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..fd64db5 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ប្រើប៊្លូធូស"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"បានភ្ជាប់"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ការស្ដាប់សំឡេងរួមគ្នា"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"បាន​រក្សាទុក"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ផ្ដាច់"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"បើកដំណើរការ"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"ការហៅទៅលេខសង្គ្រោះបន្ទាន់ ឬ SOS"</string>
     <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..94979b0 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ಬ್ಲೂಟೂತ್ ಬಳಸಿ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ಆಡಿಯೋ ಹಂಚಿಕೊಳ್ಳುವಿಕೆ"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ಡಿಸ್‌ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
@@ -308,8 +310,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 +437,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 +467,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"ತುರ್ತು ಕರೆಗಳು ಅಥವಾ SOS"</string>
     <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..e316ffd 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"블루투스 사용"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"연결됨"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"오디오 공유"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"저장됨"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"연결 해제"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"실행"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"긴급 전화 또는 SOS"</string>
     <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..ec07794 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -110,7 +110,7 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Экранды жаздырасызбы?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Бир колдонмону жаздыруу"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Бүтүндөй экранды жаздыруу"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Бүт экранды жаздырганда экранда көрүнүп турган нерселердин баары жаздырылат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Бүтүндөй экранды жаздырганда, андагы нерселердин баары видеого түшүп калат. Андыктан этият болуп, сырсөздөр, төлөм ыкмалары, билдирүүлөр, сүрөттөр, аудио жана видео материалдар сыяктуу купуя нерселерди көрсөтүп албаңыз."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Колдонмону жаздырганда ал колдонмодо көрсөтүлүп же ойнотулуп жаткан нерселер жаздырылат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Экранды жаздыруу"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"Жаздыруу үчүн колдонмо тандоо"</string>
@@ -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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Иштетүү"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Туташты"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Чогуу угуу"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сакталды"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ажыратуу"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"иштетүү"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Шашылыш чалуулар же SOS"</string>
     <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..5ac2a6f 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ໃຊ້ Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ການແບ່ງປັນສຽງ"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ບັນທຶກແລ້ວ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ຕັດການເຊື່ອມຕໍ່"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ເປີດນຳໃຊ້"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"ໂທສຸກເສີນ ຫຼື SOS"</string>
     <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..0516504 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"„Bluetooth“ naudojimas"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Prisijungta"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Garso įrašų bendrinimas"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Išsaugota"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atjungti"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"suaktyvinti"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Skambučiai pagalbos numeriu arba pagalbos iškvietimas kritiniu atveju"</string>
     <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..baf03e2 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Izmantot Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Savienojums izveidots"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio kopīgošana"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saglabāta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"atvienot"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizēt"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Ārkārtas izsaukumi vai ārkārtas zvani"</string>
     <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..24bdae3 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Поврзано"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Споделување аудио"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Зачувано"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекини врска"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирај"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Итни повици или SOS"</string>
     <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..32f840e 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ഉപയോഗിക്കുക"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"കണക്‌റ്റ് ചെയ്‌തു"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ഓഡിയോ പങ്കിടൽ"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"സംരക്ഷിച്ചു"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"വിച്ഛേദിക്കുക"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"സജീവമാക്കുക"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"എമർജൻസി കോൾ അല്ലെങ്കിൽ SOS"</string>
     <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..d229589 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth-г ашиглах"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Холбогдсон"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Аудио хуваалцах"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Хадгалсан"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"салгах"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"идэвхжүүлэх"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Яаралтай дуудлага эсвэл SOS"</string>
     <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..5ec388d 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्‍लूटूथ वापरा"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट केले"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ऑडिओ शेअरिंग"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेव्ह केले"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट करा"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ॲक्टिव्हेट करा"</string>
@@ -308,8 +310,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 +393,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"आणीबाणी कॉल किंवा SOS"</string>
     <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..9a9455a 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gunakan Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Disambungkan"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Perkongsian Audio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan sambungan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string>
@@ -308,8 +310,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 +382,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Panggilan kecemasan atau SOS"</string>
     <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..622b1d5 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ဘလူးတုသ်သုံးရန်"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ချိတ်ဆက်ထားသည်"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"အော်ဒီယို မျှဝေခြင်း"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"သိမ်းထားသည်"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"စသုံးရန်"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"အရေးပေါ်ဖုန်းခေါ်ခြင်း (သို့) SOS"</string>
     <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 +1394,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..1191844 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bruk Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Tilkoblet"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Lyddeling"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Lagret"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koble fra"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiver"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Nødanrop eller SOS"</string>
     <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..610a449 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ब्लुटुथ प्रयोग गर्नुहोस्"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"कनेक्ट गरिएको छ"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"अडियो सेयरिङ"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"सेभ गरिएको छ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"डिस्कनेक्ट गर्नुहोस्"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"एक्टिभेट गर्नुहोस्"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"आपत्कालीन कल वा SOS"</string>
     <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-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 21f1cfb..c1eff5f 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -16,7 +16,11 @@
 
   NOTE: You might also want to edit: core/res/res/values-night/*.xml
   -->
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <!-- The dark background color behind the shade -->
+    <color name="shade_scrim_background_dark">@androidprv:color/system_under_surface_dark</color>
+
     <!-- The color of the legacy notifications with customs backgrounds (gingerbread and lollipop.)
     It's fine to override this color since at that point the shade was dark. -->
     <color name="notification_legacy_background_color">@color/GM2_grey_900</color>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 36f88a0..404d768 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth gebruiken"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Verbonden"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio delen"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Opgeslagen"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"loskoppelen"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activeren"</string>
@@ -308,8 +310,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 +382,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 +437,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>
@@ -629,7 +631,7 @@
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Instellingen"</string>
     <string name="volume_panel_captioning_title" msgid="5984936949147684357">"Live ondertiteling"</string>
     <string name="csd_lowered_title" product="default" msgid="2464112924151691129">"Volume verlaagd naar een veiliger niveau"</string>
-    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"Het hoofdtelefoonvolume is langer dan de aanbevolen tijd hoog geweest"</string>
+    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"Het koptelefoonvolume is langer dan de aanbevolen tijd hoog geweest"</string>
     <string name="csd_500_system_lowered_text" product="default" msgid="7414943302186884124">"Het hoofdtelefoonvolume overschrijdt de veiligheidslimiet voor deze week"</string>
     <string name="csd_button_keep_listening" product="default" msgid="4093794049149286784">"Blijven luisteren"</string>
     <string name="csd_button_lower_volume" product="default" msgid="5347210412376264579">"Volume omlaag"</string>
@@ -718,6 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Noodoproepen of SOS"</string>
     <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..5b9d747 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ବ୍ଲୁଟୁଥ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"କନେକ୍ଟ କରାଯାଇଛି"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ଅଡିଓ ସେୟାରିଂ"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ସେଭ କରାଯାଇଛି"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ଚାଲୁ କରନ୍ତୁ"</string>
@@ -308,8 +310,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 +385,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"ଜରୁରୀକାଳୀନ କଲ କିମ୍ବା SOS"</string>
     <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..c9a45ba 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -110,7 +110,7 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ਕੀ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰਨਾ ਹੈ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ਇੱਕ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
-    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ਜਦੋਂ ਤੁਸੀਂ ਆਪਣੀ ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰ ਰਹੇ ਹੁੰਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਾਈ ਜਾ ਰਹੀ ਹਰ ਚੀਜ਼ ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+    <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ਜਦੋਂ ਤੁਸੀਂ ਆਪਣੀ ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰ ਰਹੇ ਹੁੰਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਾਈ ਜਾ ਰਹੀ ਹਰ ਚੀਜ਼ ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਨਾਲ ਹੀ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰ ਰਹੇ ਹੁੰਦੇ ਹੋ, ਤਾਂ ਉਸ ਐਪ ਵਿੱਚ ਦਿਖਾਈ ਜਾਂ ਚਲਾਈ ਜਾ ਰਹੀ ਹਰ ਚੀਜ਼ ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਕਰੋ"</string>
     <string name="screenrecord_app_selector_title" msgid="3854492366333954736">"ਰਿਕਾਰਡ ਕਰਨ ਲਈ ਐਪ ਚੁਣੋ"</string>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ਬਲੂਟੁੱਥ ਵਰਤੋ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"ਕਨੈਕਟ ਹੈ"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ਆਡੀਓ ਸਾਂਝਾਕਰਨ"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ਕਿਰਿਆਸ਼ੀਲ ਕਰੋ"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"ਐਮਰਜੈਂਸੀ ਕਾਲਾਂ ਜਾਂ ਸਹਾਇਤਾ"</string>
     <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..f467409 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -19,7 +19,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_label" msgid="4811759950673118541">"UI systemu"</string>
+    <string name="app_label" msgid="4811759950673118541">"Interfejs systemu"</string>
     <string name="battery_low_title" msgid="5319680173344341779">"Włączyć Oszczędzanie baterii?"</string>
     <string name="battery_low_description" msgid="3282977755476423966">"Masz już tylko <xliff:g id="PERCENTAGE">%s</xliff:g> baterii. Oszczędzanie baterii uruchamia ciemny motyw, ogranicza aktywność w tle i opóźnia powiadomienia."</string>
     <string name="battery_low_intro" msgid="5148725009653088790">"Oszczędzanie baterii uruchamia ciemny motyw, ogranicza aktywność w tle i opóźnia powiadomienia."</string>
@@ -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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Używaj Bluetootha"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Połączone"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Udostępnianie dźwięku"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Zapisane"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"rozłącz"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktywuj"</string>
@@ -308,8 +310,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 +379,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Połączenia alarmowe lub SOS"</string>
     <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..154fc3d 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartilhamento de áudio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
@@ -308,8 +310,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 +391,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Chamadas de emergência ou SOS"</string>
     <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..d96c540 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ligado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Partilha de áudio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desassociar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
@@ -308,8 +310,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 +386,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Chamadas de emergência ou SOS"</string>
     <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..154fc3d 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Usar Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectado"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Compartilhamento de áudio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvo"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"ativar"</string>
@@ -308,8 +310,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 +391,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Chamadas de emergência ou SOS"</string>
     <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..5e1eee3 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Folosește Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Conectat"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Permiterea accesului la audio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Salvat"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"deconectează"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activează"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Apeluri de urgență sau SOS"</string>
     <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..e938ca0 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Использовать"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Подключено"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Отправка аудио"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сохранено"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"отключить"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активировать"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Экстренные вызовы или спутниковый SOS"</string>
     <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..7b2b2be 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"බ්ලූටූත් භාවිතා කරන්න"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"සම්බන්ධිතයි"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ශ්‍රව්‍ය බෙදා ගැනීම"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"සුරැකිණි"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"විසන්ධි කරන්න"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"සක්‍රිය කරන්න"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"හදිසි ඇමතුම් හෝ SOS"</string>
     <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..877e87b 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Používať Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Pripojené"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Zdieľanie zvuku"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uložené"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojiť"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovať"</string>
@@ -308,8 +310,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 +382,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Tiesňové volania alebo pomoc v tiesni"</string>
     <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..22c5293 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Uporabi Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Deljenje zvoka"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Shranjeno"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekinitev povezave"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviranje"</string>
@@ -308,8 +310,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 +391,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 +437,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 +675,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Klici v sili ali SOS prek satelita"</string>
     <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..a864df9 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Përdor Bluetooth-in"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Lidhur"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ndarja e audios"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ruajtur"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"shkëput"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizo"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Telefonatat e urgjencës ose SOS"</string>
     <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..91fdf2a 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Користи Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Повезано"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Дељење звука"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Сачувано"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"прекините везу"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активирајте"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Хитни позиви или хитна помоћ"</string>
     <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..dfd65bb 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Använd Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ansluten"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ljuddelning"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Sparad"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"koppla från"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivera"</string>
@@ -308,8 +310,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 +382,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Nödsamtal eller SOS"</string>
     <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..809d2c4 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -124,7 +124,7 @@
     <string name="screenrecord_ongoing_screen_and_audio" msgid="5351133763125180920">"Inarekodi skrini na sauti"</string>
     <string name="screenrecord_taps_label" msgid="1595690528298857649">"Onyesha sehemu za kugusa kwenye skrini"</string>
     <string name="screenrecord_stop_label" msgid="72699670052087989">"Acha"</string>
-    <string name="screenrecord_share_label" msgid="5025590804030086930">"Shiriki"</string>
+    <string name="screenrecord_share_label" msgid="5025590804030086930">"Tuma"</string>
     <string name="screenrecord_save_title" msgid="1886652605520893850">"Imehifadhi rekodi ya skrini"</string>
     <string name="screenrecord_save_text" msgid="3008973099800840163">"Gusa ili uangalie"</string>
     <string name="screenrecord_save_error" msgid="5862648532560118815">"Hitilafu imetokea wakati wa kuhifadhi rekodi ya skrini"</string>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Tumia Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Imeunganishwa"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Kusikiliza Pamoja"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Imehifadhiwa"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ondoa"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"anza kutumia"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Simu za dharura"</string>
     <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-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 7ba080b..ee8e11f4 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"புளூடூத்தைப் பயன்படுத்துதல்"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"இணைக்கப்பட்டது"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ஆடியோ பகிர்வு"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"சேமிக்கப்பட்டது"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"இணைப்பு நீக்கும்"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"செயல்படுத்தும்"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"அவசர அழைப்புகள் அல்லது SOS"</string>
     <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..d09f3ec 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"బ్లూటూత్ వాడండి"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"కనెక్ట్ అయింది"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ఆడియో షేరింగ్"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"సేవ్ అయ్యింది"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"డిస్‌కనెక్ట్ చేయండి"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"యాక్టివేట్ చేయండి"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"ఎమర్జెన్సీ కాల్స్ లేదా SOS"</string>
     <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..ddea72f 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"ใช้บลูทูธ"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"เชื่อมต่อแล้ว"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"การแชร์เสียง"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"บันทึกแล้ว"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ยกเลิกการเชื่อมต่อ"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"เปิดใช้งาน"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"การโทรฉุกเฉินหรือ SOS"</string>
     <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..7247757 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Gumamit ng Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Nakakonekta"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Pag-share ng Audio"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Na-save"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"idiskonekta"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"i-activate"</string>
@@ -308,8 +310,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 +437,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>
@@ -629,7 +631,7 @@
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Mga Setting"</string>
     <string name="volume_panel_captioning_title" msgid="5984936949147684357">"Instant Caption"</string>
     <string name="csd_lowered_title" product="default" msgid="2464112924151691129">"Ibinaba sa mas ligtas na level ang volume"</string>
-    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"Naging malakas ang volume ng headphones nang mas matagal sa inirerekomenda"</string>
+    <string name="csd_system_lowered_text" product="default" msgid="1250251883692996888">"Malakas ang volume ng headphones nang mas matagal sa inirerekomenda"</string>
     <string name="csd_500_system_lowered_text" product="default" msgid="7414943302186884124">"Lampas na sa ligtas na limitasyon para sa linggong ito ang volume ng headphone"</string>
     <string name="csd_button_keep_listening" product="default" msgid="4093794049149286784">"Magpatuloy sa pakikinig"</string>
     <string name="csd_button_lower_volume" product="default" msgid="5347210412376264579">"Hinaan"</string>
@@ -718,6 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Mga emergency na tawag o SOS"</string>
     <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..067d207 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth\'u kullan"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Bağlandı"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ses Paylaşımı"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Kaydedildi"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"bağlantıyı kes"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"etkinleştir"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Acil durum aramaları veya acil yardım"</string>
     <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..7ffb958 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Увімкнути Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Підключено"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Надсилання аудіо"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Збережено"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"від’єднати"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"активувати"</string>
@@ -308,8 +310,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 +382,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 +392,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Екстрені виклики або сигнал SOS"</string>
     <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..b2d4a11 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"بلوٹوتھ استعمال کریں"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"منسلک ہے"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"آڈیو کا اشتراک"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"محفوظ ہے"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"غیر منسلک کریں"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"فعال کریں"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"‏ایمرجنسی کالز یا SOS"</string>
     <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..4b488a25 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth ishlatish"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ulangan"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Audio ulashuvi"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Saqlangan"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"uzish"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"faollashtirish"</string>
@@ -308,8 +310,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 +382,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Favqulodda chaqiruvlar yoki SOS"</string>
     <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..2e06f72 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bật Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Đã kết nối"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Chia sẻ âm thanh"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Đã lưu"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"ngắt kết nối"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"kích hoạt"</string>
@@ -308,8 +310,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 +382,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Cuộc gọi khẩn cấp hoặc SOS"</string>
     <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..0a96fc2 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -107,7 +107,7 @@
     <string name="screenrecord_title" msgid="4257171601439507792">"屏幕录制器"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"正在处理屏幕录制视频"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"持续显示屏幕录制会话通知"</string>
-    <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"要录制屏幕?"</string>
+    <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"要录制屏幕吗?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"录制单个应用"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"录制整个屏幕"</string>
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"录制整个屏幕时,屏幕上显示的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款信息、消息、照片、音频、视频等。"</string>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"启用蓝牙"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已连接"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音频分享"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已保存"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"断开连接"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"启用"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"紧急呼叫或紧急求救"</string>
     <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..6b5edff 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連接"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音訊分享功能"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"解除連結"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟動"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"緊急電話或 SOS"</string>
     <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..78cd289 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>
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"使用藍牙"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"已連線"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音訊分享"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"已儲存"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"取消連結"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"啟用"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"緊急電話或緊急求救"</string>
     <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..8bcc3ed 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -300,6 +300,8 @@
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Sebenzisa iBluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Ixhunyiwe"</string>
     <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Ukwabelana Ngokuqoshiwe"</string>
+    <!-- no translation found for quick_settings_bluetooth_device_audio_sharing_or_switch_active (3227408556754456024) -->
+    <skip />
     <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ilondoloziwe"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"nqamula"</string>
     <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"yenza kusebenze"</string>
@@ -308,8 +310,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 +437,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 +720,7 @@
     <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>
+    <string name="satellite_emergency_only_carrier_text" msgid="828510231597991206">"Ikholi ephuthumayo noma i-SOS"</string>
     <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/colors.xml b/packages/SystemUI/res/values/colors.xml
index 8cf0fb2..a375264 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -29,7 +29,7 @@
     <color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black -->
 
     <!-- The dark background color behind the shade -->
-    <color name="shade_scrim_background_dark">@*android:color/black</color>
+    <color name="shade_scrim_background_dark">@androidprv:color/system_under_surface_light</color>
 
     <!-- The color of the background in the separated list of the Global Actions menu -->
     <color name="global_actions_separated_background">#F5F5F5</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 823ff9f..38ef0e9 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">
@@ -1048,9 +1052,6 @@
     <!-- The width of the shortcut helper container, as a fraction of the screen's width. -->
     <item name="shortcut_helper_screen_width_fraction" format="float" type="dimen">1.0</item>
 
-    <!-- Only applicable for dual shade - Allow Notifications/QS shade to anchor to the bottom. -->
-    <bool name="config_dualShadeAlignedToBottom">false</bool>
-
     <!-- List of packages for which we want to use activity info (instead of application info) for biometric prompt logo. Empty for AOSP. [DO NOT TRANSLATE] -->
     <string-array name="config_useActivityLogoForBiometricPrompt" translatable="false"/>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e5750d2..141d035 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1751,6 +1751,7 @@
 
     <!-- System UI Dialog -->
     <dimen name="dialog_title_text_size">24sp</dimen>
+    <dimen name="dialog_button_side_margin">8dp</dimen>
 
     <!-- Internet panel related dimensions -->
     <dimen name="internet_dialog_list_max_height">662dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 18b7073..be74291 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -270,6 +270,7 @@
     <!-- Add to note button used in App Clips flow to return the saved screenshot image to notes app. [CHAR LIMIT=NONE] -->
     <string name="app_clips_save_add_to_note">Add to note</string>
     <string name="backlinks_include_link">Include link</string>
+    <string name="backlinks_duplicate_label_format"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> <xliff:g id="frequencyCount" example="(1)">(%2$d)</xliff:g></string>
 
     <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
     <string name="screenrecord_title">Screen Recorder</string>
@@ -720,8 +721,8 @@
     <!-- QuickSettings: Do not disturb - Priority only [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Do not disturb - Alarms only [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Do not disturb - Total silence [CHAR LIMIT=NONE] -->
-    <!-- QuickSettings: Priority modes [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_modes_label">Priority modes</string>
+    <!-- QuickSettings: Modes [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_modes_label">Modes</string>
     <!-- QuickSettings: Bluetooth [CHAR LIMIT=NONE] -->
     <string name="quick_settings_bluetooth_label">Bluetooth</string>
     <!-- QuickSettings: Bluetooth (Multiple) [CHAR LIMIT=NONE] -->
@@ -740,6 +741,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]-->
@@ -1094,28 +1097,28 @@
     <!-- QuickStep: Accessibility to toggle overview [CHAR LIMIT=40] -->
     <string name="quick_step_accessibility_toggle_overview">Toggle Overview</string>
 
-    <!-- Priority modes dialog title [CHAR LIMIT=35] -->
-    <string name="zen_modes_dialog_title">Priority modes</string>
+    <!-- Modes dialog title [CHAR LIMIT=35] -->
+    <string name="zen_modes_dialog_title">Modes</string>
 
-    <!-- Priority modes dialog confirmation button [CHAR LIMIT=15] -->
+    <!-- Modes dialog confirmation button [CHAR LIMIT=15] -->
     <string name="zen_modes_dialog_done">Done</string>
 
-    <!-- Priority modes dialog settings shortcut button [CHAR LIMIT=15] -->
+    <!-- Modes dialog settings shortcut button [CHAR LIMIT=15] -->
     <string name="zen_modes_dialog_settings">Settings</string>
 
-    <!-- Priority modes: label for an active mode [CHAR LIMIT=35] -->
+    <!-- Modes: label for an active mode [CHAR LIMIT=35] -->
     <string name="zen_mode_on">On</string>
 
-    <!-- Priority modes: label for an active mode, with details [CHAR LIMIT=10] -->
+    <!-- Modes: label for an active mode, with details [CHAR LIMIT=10] -->
     <string name="zen_mode_on_with_details">On • <xliff:g id="trigger_description" example="Mon-Fri, 23:00-7:00">%1$s</xliff:g></string>
 
-    <!-- Priority modes: label for an inactive mode [CHAR LIMIT=35] -->
+    <!-- Modes: label for an inactive mode [CHAR LIMIT=35] -->
     <string name="zen_mode_off">Off</string>
 
-    <!-- Priority modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
+    <!-- Modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
     <string name="zen_mode_set_up">Set up</string>
 
-    <!-- Priority modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
+    <!-- Modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
     <string name="zen_mode_no_manual_invocation">Manage in settings</string>
 
     <string name="zen_mode_active_modes">
@@ -1377,9 +1380,13 @@
     <string name="media_projection_entry_app_permission_dialog_title">Share your screen with <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>?</string>
 
     <!-- 1P/3P app media projection permission option for capturing just a single app [CHAR LIMIT=50] -->
-    <string name="media_projection_entry_app_permission_dialog_option_text_single_app">Share one app</string>
+    <string name="screen_share_permission_dialog_option_single_app">Share one app</string>
+    <!-- CTS tests rely on the `screen_share_permission_dialog_option_single_app` resource name, so just point the updated resource name to the old resource name. -->
+    <string name="media_projection_entry_app_permission_dialog_option_text_single_app">@string/screen_share_permission_dialog_option_single_app</string>
     <!-- 1P/3P app media projection permission option for capturing the whole screen [CHAR LIMIT=50] -->
-    <string name="media_projection_entry_app_permission_dialog_option_text_entire_screen">Share entire screen</string>
+    <string name="screen_share_permission_dialog_option_entire_screen">Share entire screen</string>
+    <!-- CTS tests rely on the `screen_share_permission_dialog_option_entire_screen` resource name, so just point the updated resource name to the old resource name. -->
+    <string name="media_projection_entry_app_permission_dialog_option_text_entire_screen">@string/screen_share_permission_dialog_option_entire_screen</string>
     <!-- 1P/3P app media projection permission warning for capturing the whole screen. [CHAR LIMIT=350] -->
     <string name="media_projection_entry_app_permission_dialog_warning_entire_screen">When you’re sharing your entire screen, anything on your screen is visible to <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g>. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
     <!-- 1P/3P app media projection permission warning for capturing an app. [CHAR LIMIT=350] -->
@@ -1869,6 +1876,8 @@
 
     <!-- Text displayed indicating that the user is connected to a satellite signal. -->
     <string name="satellite_connected_carrier_text">Satellite SOS</string>
+    <!-- Text displayed indicating that the user might be able to use satellite SOS. -->
+    <string name="satellite_emergency_only_carrier_text">Emergency calls or SOS</string>
 
     <!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
     <string name="accessibility_managed_profile">Work profile</string>
@@ -3690,6 +3699,22 @@
           -->
     <string name="shortcut_helper_key_combinations_or_separator">or</string>
 
+    <!-- Keyboard touchpad tutorial scheduler-->
+    <!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
+    <string name="launch_keyboard_tutorial_notification_title">Navigate using your keyboard</string>
+    <!-- Notification text for launching keyboard tutorial [CHAR_LIMIT=100] -->
+    <string name="launch_keyboard_tutorial_notification_content">Learn keyboards shortcuts</string>
+
+    <!-- Notification title for launching touchpad tutorial [CHAR_LIMIT=100] -->
+    <string name="launch_touchpad_tutorial_notification_title">Navigate using your touchpad</string>
+    <!-- Notification text for launching keyboard tutorial [CHAR_LIMIT=100] -->
+    <string name="launch_touchpad_tutorial_notification_content">Learn touchpad gestures</string>
+
+    <!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
+    <string name="launch_keyboard_touchpad_tutorial_notification_title">Navigate using your keyboard and touchpad</string>
+    <!-- Notification text for launching keyboard tutorial [CHAR_LIMIT=100] -->
+    <string name="launch_keyboard_touchpad_tutorial_notification_content">Learn touchpad gestures, keyboards shortcuts, and more</string>
+
     <!-- TOUCHPAD TUTORIAL-->
     <!-- Label for button opening tutorial for back gesture on touchpad [CHAR LIMIT=NONE] -->
     <string name="touchpad_tutorial_back_gesture_button">Back gesture</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 3ef6243..a02c354 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -505,14 +505,14 @@
         <item name="onSurfaceVariant">?androidprv:attr/materialColorOnSurfaceVariant</item>
         <item name="outline">?androidprv:attr/materialColorOutline</item>
 
-        <item name="shadeActive">@color/material_dynamic_primary90</item>
-        <item name="onShadeActive">@color/material_dynamic_primary10</item>
-        <item name="onShadeActiveVariant">@color/material_dynamic_primary30</item>
-        <item name="shadeInactive">@color/material_dynamic_neutral20</item>
-        <item name="onShadeInactive">@color/material_dynamic_neutral90</item>
-        <item name="onShadeInactiveVariant">@color/material_dynamic_neutral_variant80</item>
-        <item name="shadeDisabled">@color/shade_disabled</item>
-        <item name="underSurface">@color/material_dynamic_neutral0</item>
+        <item name="shadeActive">?androidprv:attr/customColorShadeActive</item>
+        <item name="onShadeActive">?androidprv:attr/customColorOnShadeActive</item>
+        <item name="onShadeActiveVariant">?androidprv:attr/customColorOnShadeActiveVariant</item>
+        <item name="shadeInactive">?androidprv:attr/customColorShadeInactive</item>
+        <item name="onShadeInactive">?androidprv:attr/customColorOnShadeInactive</item>
+        <item name="onShadeInactiveVariant">?androidprv:attr/customColorOnShadeInactiveVariant</item>
+        <item name="shadeDisabled">?androidprv:attr/customColorShadeDisabled</item>
+        <item name="underSurface">?androidprv:attr/customColorUnderSurface</item>
         <item name="android:itemTextAppearance">@style/Control.MenuItem</item>
     </style>
 
diff --git a/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json
new file mode 100644
index 0000000..fe996b7
--- /dev/null
+++ b/packages/SystemUI/schemas/com.android.systemui.communal.data.db.CommunalDatabase/3.json
@@ -0,0 +1,81 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 3,
+    "identityHash": "02e2da2d36e6955200edd5fb49e63c72",
+    "entities": [
+      {
+        "tableName": "communal_widget_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `widget_id` INTEGER NOT NULL, `component_name` TEXT NOT NULL, `item_id` INTEGER NOT NULL, `user_serial_number` INTEGER NOT NULL DEFAULT -1)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "widgetId",
+            "columnName": "widget_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "componentName",
+            "columnName": "component_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "itemId",
+            "columnName": "item_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "userSerialNumber",
+            "columnName": "user_serial_number",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "-1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        }
+      },
+      {
+        "tableName": "communal_item_rank_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `rank` INTEGER NOT NULL DEFAULT 0)",
+        "fields": [
+          {
+            "fieldPath": "uid",
+            "columnName": "uid",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "rank",
+            "columnName": "rank",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "uid"
+          ]
+        }
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '02e2da2d36e6955200edd5fb49e63c72')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index fbe1399..8f55961 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -48,7 +48,7 @@
         "src/**/*.kt",
         "src/**/*.aidl",
         ":wm_shell-aidls",
-        ":wm_shell_util-sources",
+        ":wm_shell-shared-aidls",
     ],
     static_libs: [
         "BiometricsSharedLib",
@@ -69,6 +69,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/AuthInteractionProperties.kt b/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.kt
new file mode 100644
index 0000000..efa13c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AuthInteractionProperties.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.os.VibrationAttributes
+import com.google.android.msdl.domain.InteractionProperties
+
+/**
+ * This class represents the set of [InteractionProperties] that only hold [VibrationAttributes] for
+ * the case of user authentication.
+ */
+data class AuthInteractionProperties(
+    override val vibrationAttributes: VibrationAttributes =
+        VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST)
+) : InteractionProperties
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 5dcf161..c1eae2e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -477,6 +477,12 @@
         smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
     }
 
+    fun setFallbackWeatherData(data: WeatherData) {
+        if (weatherData != null) return
+        weatherData = data
+        clock?.run { events.onWeatherDataChanged(data) }
+    }
+
     /**
      * Sets this clock as showing in a secondary display.
      *
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index 7733841..5e36539 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.Flags.msdlFeedback;
 
 import android.annotation.SuppressLint;
 import android.app.ActivityOptions;
@@ -46,6 +47,9 @@
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.ViewController;
 
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -67,6 +71,7 @@
     private final Executor mMainExecutor;
     private final Executor mBackgroundExecutor;
     private final SelectedUserInteractor mSelectedUserInteractor;
+    private final MSDLPlayer mMSDLPlayer;
 
     private final KeyguardUpdateMonitorCallback mInfoCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -99,7 +104,8 @@
             MetricsLogger metricsLogger,
             LockPatternUtils lockPatternUtils,
             Executor mainExecutor, Executor backgroundExecutor,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor,
+            MSDLPlayer msdlPlayer) {
         super(view);
         mConfigurationController = configurationController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -112,6 +118,7 @@
         mMainExecutor = mainExecutor;
         mBackgroundExecutor = backgroundExecutor;
         mSelectedUserInteractor = selectedUserInteractor;
+        mMSDLPlayer = msdlPlayer;
     }
 
     @Override
@@ -165,6 +172,9 @@
     @SuppressLint("MissingPermission")
     public void takeEmergencyCallAction() {
         mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL);
+        if (msdlFeedback()) {
+            mMSDLPlayer.playToken(MSDLToken.KEYPRESS_RETURN, null);
+        }
         if (mPowerManager != null) {
             mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
         }
@@ -221,6 +231,7 @@
         private final Executor mMainExecutor;
         private final Executor mBackgroundExecutor;
         private final SelectedUserInteractor mSelectedUserInteractor;
+        private final MSDLPlayer mMSDLPlayer;
 
         @Inject
         public Factory(ConfigurationController configurationController,
@@ -233,7 +244,8 @@
                 LockPatternUtils lockPatternUtils,
                 @Main Executor mainExecutor,
                 @Background Executor backgroundExecutor,
-                SelectedUserInteractor selectedUserInteractor) {
+                SelectedUserInteractor selectedUserInteractor,
+                MSDLPlayer msdlPlayer) {
 
             mConfigurationController = configurationController;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -246,6 +258,7 @@
             mMainExecutor = mainExecutor;
             mBackgroundExecutor = backgroundExecutor;
             mSelectedUserInteractor = selectedUserInteractor;
+            mMSDLPlayer = msdlPlayer;
         }
 
         /** Construct an {@link com.android.keyguard.EmergencyButtonController}. */
@@ -253,7 +266,7 @@
             return new EmergencyButtonController(view, mConfigurationController,
                     mKeyguardUpdateMonitor, mPowerManager, mActivityTaskManager, mShadeController,
                     mTelecomManager, mMetricsLogger, mLockPatternUtils, mMainExecutor,
-                    mBackgroundExecutor, mSelectedUserInteractor);
+                    mBackgroundExecutor, mSelectedUserInteractor, mMSDLPlayer);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index dad4400..28f1381 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -19,7 +19,10 @@
 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
 import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT;
+import static com.android.systemui.Flags.msdlFeedback;
+import static com.android.systemui.Flags.notifyPasswordTextViewUserActivityInBackground;
 
+import android.annotation.Nullable;
 import android.content.res.ColorStateList;
 import android.os.AsyncTask;
 import android.os.CountDownTimer;
@@ -40,6 +43,9 @@
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -50,11 +56,14 @@
     private final LatencyTracker mLatencyTracker;
     private final FalsingCollector mFalsingCollector;
     private final EmergencyButtonController mEmergencyButtonController;
+    private final UserActivityNotifier mUserActivityNotifier;
     private CountDownTimer mCountdownTimer;
     private boolean mDismissing;
     protected AsyncTask<?, ?, ?> mPendingLockCheck;
     protected boolean mResumed;
     protected boolean mLockedOut;
+    @Nullable
+    protected MSDLPlayer mMSDLPlayer;
 
     private final KeyDownListener mKeyDownListener = (keyCode, keyEvent) -> {
         // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN.
@@ -81,7 +90,9 @@
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController,
-            FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor) {
+            FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
+            @Nullable MSDLPlayer msdlPlayer,
+            UserActivityNotifier userActivityNotifier) {
         super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
                 messageAreaControllerFactory, featureFlags, selectedUserInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -89,6 +100,8 @@
         mLatencyTracker = latencyTracker;
         mFalsingCollector = falsingCollector;
         mEmergencyButtonController = emergencyButtonController;
+        mMSDLPlayer = msdlPlayer;
+        mUserActivityNotifier = userActivityNotifier;
     }
 
     abstract void resetState();
@@ -178,6 +191,7 @@
     void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) {
         boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId;
         if (matched) {
+            playAuthenticationHaptics(/* unlock= */true);
             getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
             if (dismissKeyguard) {
                 mDismissing = true;
@@ -185,6 +199,7 @@
                 getKeyguardSecurityCallback().dismiss(true, userId, getSecurityMode());
             }
         } else {
+            playAuthenticationHaptics(/* unlock= */false);
             mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
             if (isValidPassword) {
                 getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
@@ -201,6 +216,18 @@
         }
     }
 
+    private void playAuthenticationHaptics(boolean unlock) {
+        if (!msdlFeedback() || mMSDLPlayer == null) return;
+
+        MSDLToken token;
+        if (unlock) {
+            token = MSDLToken.UNLOCK;
+        } else {
+            token = MSDLToken.FAILURE;
+        }
+        mMSDLPlayer.playToken(token, mAuthInteractionProperties);
+    }
+
     protected void startErrorAnimation() { /* no-op */ }
 
     protected void verifyPasswordAndUnlock() {
@@ -280,6 +307,9 @@
         getKeyguardSecurityCallback().userActivity();
         getKeyguardSecurityCallback().onUserInput();
         mMessageAreaController.setMessage("");
+        if (notifyPasswordTextViewUserActivityInBackground()) {
+            mUserActivityNotifier.notifyUserActivity();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index db14a0f..dd84bc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -45,6 +45,9 @@
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
+import com.google.android.msdl.domain.InteractionProperties;
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import javax.inject.Inject;
 
 /** Controller for a {@link KeyguardSecurityView}. */
@@ -63,6 +66,8 @@
     private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {};
     private final FeatureFlags mFeatureFlags;
     protected final SelectedUserInteractor mSelectedUserInteractor;
+    protected final InteractionProperties mAuthInteractionProperties =
+            new AuthInteractionProperties();
 
     protected KeyguardInputViewController(T view, SecurityMode securityMode,
             KeyguardSecurityCallback keyguardSecurityCallback,
@@ -214,6 +219,8 @@
         private final SelectedUserInteractor mSelectedUserInteractor;
         private final UiEventLogger mUiEventLogger;
         private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
+        private final MSDLPlayer mMSDLPlayer;
+        private final UserActivityNotifier mUserActivityNotifier;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -228,7 +235,9 @@
                 KeyguardViewController keyguardViewController,
                 FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
                 UiEventLogger uiEventLogger,
-                KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+                KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+                MSDLPlayer msdlPlayer,
+                UserActivityNotifier userActivityNotifier) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -246,6 +255,8 @@
             mSelectedUserInteractor = selectedUserInteractor;
             mUiEventLogger = uiEventLogger;
             mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
+            mMSDLPlayer = msdlPlayer;
+            mUserActivityNotifier = userActivityNotifier;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -268,29 +279,29 @@
                         mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
                         mFalsingCollector, mKeyguardViewController,
                         mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
-                        mKeyguardKeyboardInteractor);
+                        mKeyguardKeyboardInteractor, mMSDLPlayer, mUserActivityNotifier);
             } else if (keyguardInputView instanceof KeyguardPINView) {
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
                         mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
-                        mUiEventLogger, mKeyguardKeyboardInteractor
-                );
+                        mUiEventLogger, mKeyguardKeyboardInteractor, mMSDLPlayer,
+                        mUserActivityNotifier);
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
                         emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
-                        mKeyguardKeyboardInteractor);
+                        mKeyguardKeyboardInteractor, mMSDLPlayer, mUserActivityNotifier);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
                         emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
-                        mKeyguardKeyboardInteractor
+                        mKeyguardKeyboardInteractor, mMSDLPlayer, mUserActivityNotifier
                 );
             }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 490ad5c..905fa09 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -19,9 +19,13 @@
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.TextUtils;
@@ -52,6 +56,8 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import java.util.List;
 
 public class KeyguardPasswordViewController
@@ -131,10 +137,13 @@
             DevicePostureController postureController,
             FeatureFlags featureFlags,
             SelectedUserInteractor selectedUserInteractor,
-            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+            @Nullable MSDLPlayer msdlPlayer,
+            UserActivityNotifier userActivityNotifier) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
-                emergencyButtonController, featureFlags, selectedUserInteractor);
+                emergencyButtonController, featureFlags, selectedUserInteractor, msdlPlayer,
+                userActivityNotifier);
         mKeyguardSecurityCallback = keyguardSecurityCallback;
         mInputMethodManager = inputMethodManager;
         mPostureController = postureController;
@@ -170,8 +179,33 @@
         mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
         mPasswordEntry.setOnKeyListener(mKeyListener);
         mPasswordEntry.addTextChangedListener(mTextWatcher);
+
         // Poke the wakelock any time the text is selected or modified
-        mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity());
+        // TODO(b/362362385): Revert to the previous onClickListener implementation once this bug is
+        //  fixed.
+        mPasswordEntry.setOnClickListener(new View.OnClickListener() {
+
+            private final boolean mAutomotiveAndVisibleBackgroundUsers =
+                    isAutomotiveAndVisibleBackgroundUsers();
+
+            @Override
+            public void onClick(View v) {
+                if (mAutomotiveAndVisibleBackgroundUsers) {
+                    mInputMethodManager.restartInput(v);
+                }
+                mKeyguardSecurityCallback.userActivity();
+            }
+
+            private boolean isAutomotiveAndVisibleBackgroundUsers() {
+                final Context context = getContext();
+                return context.getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_AUTOMOTIVE)
+                        && UserManager.isVisibleBackgroundUsersEnabled()
+                        && context.getResources().getBoolean(
+                        android.R.bool.config_perDisplayFocusEnabled);
+            }
+        });
+
         mSwitchImeButton.setOnClickListener(v -> {
             mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer
             // Do not show auxiliary subtypes in password lock screen.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 0f61233..f575cf2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -16,9 +16,11 @@
 
 package com.android.keyguard;
 
+import static com.android.systemui.Flags.msdlFeedback;
 import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
+import android.annotation.Nullable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.StateListDrawable;
@@ -40,6 +42,9 @@
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
 public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
         extends KeyguardAbsKeyInputViewController<T> {
 
@@ -77,10 +82,13 @@
             FalsingCollector falsingCollector,
             FeatureFlags featureFlags,
             SelectedUserInteractor selectedUserInteractor,
-            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+            @Nullable MSDLPlayer msdlPlayer,
+            UserActivityNotifier userActivityNotifier) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
-                emergencyButtonController, featureFlags, selectedUserInteractor);
+                emergencyButtonController, featureFlags, selectedUserInteractor, msdlPlayer,
+                userActivityNotifier);
         mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
         mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
@@ -102,12 +110,22 @@
                 return false;
             });
             button.setAnimationEnabled(showAnimations);
+            button.setMSDLPlayer(mMSDLPlayer);
         }
         mPasswordEntry.setOnKeyListener(mOnKeyListener);
         mPasswordEntry.setUserActivityListener(this::onUserInput);
 
         View deleteButton = mView.findViewById(R.id.delete_button);
-        deleteButton.setOnTouchListener(mActionButtonTouchListener);
+        if (msdlFeedback()) {
+            deleteButton.setOnTouchListener((View view, MotionEvent event) -> {
+                if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mMSDLPlayer != null) {
+                    mMSDLPlayer.playToken(MSDLToken.KEYPRESS_DELETE, null);
+                }
+                return false;
+            });
+        } else {
+            deleteButton.setOnTouchListener(mActionButtonTouchListener);
+        }
         deleteButton.setOnClickListener(v -> {
             // check for time-based lockouts
             if (mPasswordEntry.isEnabled()) {
@@ -119,13 +137,19 @@
             if (mPasswordEntry.isEnabled()) {
                 mView.resetPasswordText(true /* animate */, true /* announce */);
             }
-            mView.doHapticKeyClick();
+            if (msdlFeedback() && mMSDLPlayer != null) {
+                mMSDLPlayer.playToken(MSDLToken.LONG_PRESS, null);
+            } else {
+                mView.doHapticKeyClick();
+            }
             return true;
         });
 
         View okButton = mView.findViewById(R.id.key_enter);
         if (okButton != null) {
-            okButton.setOnTouchListener(mActionButtonTouchListener);
+            if (!msdlFeedback()) {
+                okButton.setOnTouchListener(mActionButtonTouchListener);
+            }
             okButton.setOnClickListener(v -> {
                 if (mPasswordEntry.isEnabled()) {
                     verifyPasswordAndUnlock();
@@ -177,6 +201,7 @@
 
         for (NumPadKey button : mView.getButtons()) {
             button.setOnTouchListener(null);
+            button.setMSDLPlayer(null);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index f4cda02..3b5bf1a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
 
+import android.annotation.Nullable;
 import android.view.View;
 
 import com.android.internal.logging.UiEvent;
@@ -33,6 +34,8 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
+import com.google.android.msdl.domain.MSDLPlayer;
+
 public class KeyguardPinViewController
         extends KeyguardPinBasedInputViewController<KeyguardPINView> {
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -61,11 +64,13 @@
             FalsingCollector falsingCollector,
             DevicePostureController postureController, FeatureFlags featureFlags,
             SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
-            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+            @Nullable MSDLPlayer msdlPlayer,
+            UserActivityNotifier userActivityNotifier) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
-                keyguardKeyboardInteractor);
+                keyguardKeyboardInteractor, msdlPlayer, userActivityNotifier);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mPostureController = postureController;
         mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index bf905db..7efe2dd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -344,7 +344,7 @@
                         R.dimen.keyguard_security_container_padding_top), getPaddingRight(),
                 getPaddingBottom());
         setBackgroundColor(Utils.getColorAttrDefaultColor(getContext(),
-                com.android.internal.R.attr.materialColorSurface));
+                com.android.internal.R.attr.materialColorSurfaceDim));
     }
 
     void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
@@ -808,7 +808,7 @@
     void reloadColors() {
         mViewMode.reloadColors();
         setBackgroundColor(Utils.getColorAttrDefaultColor(getContext(),
-                com.android.internal.R.attr.materialColorSurface));
+                com.android.internal.R.attr.materialColorSurfaceDim));
     }
 
     /** Handles density or font scale changes. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index afd42cb..2d28a18 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -35,7 +35,6 @@
 
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
-import android.app.admin.flags.Flags;
 import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -81,7 +80,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 +133,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 +320,7 @@
             }
 
             if (KeyguardWmStateRefactor.isEnabled()) {
-                mKeyguardTransitionInteractor.startDismissKeyguardTransition(
+                mKeyguardDismissTransitionInteractor.startDismissKeyguardTransition(
                         "KeyguardSecurityContainerController#finish");
             }
         }
@@ -458,7 +457,7 @@
             DeviceProvisionedController deviceProvisionedController,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
             DevicePolicyManager devicePolicyManager,
-            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            KeyguardDismissTransitionInteractor keyguardDismissTransitionInteractor,
             Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
             Provider<DeviceEntryInteractor> deviceEntryInteractor
     ) {
@@ -490,7 +489,7 @@
         mSelectedUserInteractor = selectedUserInteractor;
         mDeviceEntryInteractor = deviceEntryInteractor;
         mJavaAdapter = javaAdapter;
-        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mKeyguardDismissTransitionInteractor = keyguardDismissTransitionInteractor;
         mDeviceProvisionedController = deviceProvisionedController;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mDevicePolicyManager = devicePolicyManager;
@@ -1140,12 +1139,7 @@
             int remainingBeforeWipe, int failedAttempts) {
         int userType = USER_TYPE_PRIMARY;
         if (expiringUserId == userId) {
-            int primaryUser = UserHandle.USER_SYSTEM;
-            if (Flags.headlessSingleUserFixes()) {
-                if (mainUserId != null) {
-                    primaryUser = mainUserId;
-                }
-            }
+            int primaryUser = mainUserId != null ? mainUserId : UserHandle.USER_SYSTEM;
             // TODO: http://b/23522538
             if (expiringUserId != primaryUser) {
                 userType = USER_TYPE_SECONDARY_USER;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 3ef3418..47fe2b2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.app.AlertDialog.Builder;
 import android.app.Dialog;
@@ -48,6 +49,8 @@
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
+import com.google.android.msdl.domain.MSDLPlayer;
+
 public class KeyguardSimPinViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
     public static final String TAG = "KeyguardSimPinView";
@@ -95,11 +98,13 @@
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
             SelectedUserInteractor selectedUserInteractor,
-            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+            @Nullable MSDLPlayer msdlPlayer,
+            UserActivityNotifier userActivityNotifier) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
-                keyguardKeyboardInteractor);
+                keyguardKeyboardInteractor, msdlPlayer, userActivityNotifier);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 46225c7..c688acb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -43,6 +44,8 @@
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
+import com.google.android.msdl.domain.MSDLPlayer;
+
 public class KeyguardSimPukViewController
         extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -92,11 +95,13 @@
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
             SelectedUserInteractor selectedUserInteractor,
-            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor,
+            @Nullable MSDLPlayer msdlPlayer,
+            UserActivityNotifier userActivityNotifier) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
-                keyguardKeyboardInteractor);
+                keyguardKeyboardInteractor, msdlPlayer, userActivityNotifier);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
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/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index dcfa775..4fb80de 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -15,6 +15,7 @@
  */
 package com.android.keyguard;
 
+import static com.android.systemui.Flags.msdlFeedback;
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.NUM_PAD_KEY;
 
 import android.content.Context;
@@ -38,6 +39,9 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.res.R;
 
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
 /**
  * Viewgroup for the bouncer numpad button, specifically for digits.
  */
@@ -57,6 +61,8 @@
     @Nullable
     private NumPadAnimator mAnimator;
     private int mOrientation;
+    @Nullable
+    private MSDLPlayer mMSDLPlayer;
 
     private View.OnClickListener mListener = new View.OnClickListener() {
         @Override
@@ -221,8 +227,12 @@
 
     // Cause a VIRTUAL_KEY vibration
     public void doHapticKeyClick() {
-        performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
-                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+        if (msdlFeedback() && mMSDLPlayer != null) {
+            mMSDLPlayer.playToken(MSDLToken.KEYPRESS_STANDARD, null);
+        } else {
+            performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+        }
     }
 
     @Override
@@ -244,4 +254,8 @@
         super.onInitializeAccessibilityNodeInfo(info);
         info.setTextEntryKey(true);
     }
+
+    public void setMSDLPlayer(@Nullable MSDLPlayer player) {
+        mMSDLPlayer = player;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 85f8b48..0c4bc0e 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static com.android.systemui.Flags.notifyPasswordTextViewUserActivityInBackground;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -257,7 +259,9 @@
 
     @Override
     protected void onUserActivity() {
-        mPM.userActivity(SystemClock.uptimeMillis(), false);
+        if (!notifyPasswordTextViewUserActivityInBackground()) {
+            mPM.userActivity(SystemClock.uptimeMillis(), false);
+        }
         super.onUserActivity();
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/UserActivityNotifier.kt b/packages/SystemUI/src/com/android/keyguard/UserActivityNotifier.kt
new file mode 100644
index 0000000..9b1ddb7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/UserActivityNotifier.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.keyguard
+
+import android.os.PowerManager
+import android.os.SystemClock
+import com.android.systemui.dagger.qualifiers.UiBackground
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/** Wrapper class for notifying the system about user activity in the background. */
+class UserActivityNotifier
+@Inject
+constructor(
+    @UiBackground private val uiBgExecutor: Executor,
+    private val powerManager: PowerManager
+) {
+
+    fun notifyUserActivity() {
+        uiBgExecutor.execute {
+            powerManager.userActivity(
+                SystemClock.uptimeMillis(),
+                PowerManager.USER_ACTIVITY_EVENT_OTHER,
+                0
+            )
+        }
+    }
+}
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/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index 394f8dd..04afd86 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -408,6 +408,10 @@
         if (!isActivated()) {
             return;
         }
+        if (!(mFullscreenBorder.getBackground() instanceof GradientDrawable)) {
+            // Wear doesn't use the same magnification border background. So early return here.
+            return;
+        }
 
         float cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
         GradientDrawable backgroundDrawable = (GradientDrawable) mFullscreenBorder.getBackground();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 9b6501e..2f0ca6e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -482,6 +482,10 @@
                 } else { // mode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
                     mEditButton.setVisibility(View.VISIBLE);
                     mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
+                    if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
+                        selectedButtonIndex =
+                                windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
+                    }
                 }
                 break;
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index e4b7b7e..275147e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -21,11 +21,13 @@
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
+import android.os.Handler;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.view.Display;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IUserInitializationCompleteCallback;
 
 import androidx.annotation.MainThread;
 
@@ -68,6 +70,9 @@
     private int mBtnMode;
     private String mBtnTargets;
     private boolean mIsKeyguardVisible;
+    private boolean mIsUserInInitialization;
+    @VisibleForTesting
+    Handler mHandler;
 
     @VisibleForTesting
     final KeyguardUpdateMonitorCallback mKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@@ -86,18 +91,14 @@
         @Override
         public void onUserSwitching(int userId) {
             destroyFloatingMenu();
-        }
-
-        @Override
-        public void onUserSwitchComplete(int userId) {
-            mContext = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
-            mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
-            mBtnTargets =
-                    mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
-            handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets);
+            mIsUserInInitialization = true;
         }
     };
 
+    @VisibleForTesting
+    final UserInitializationCompleteCallback mUserInitializationCompleteCallback =
+            new UserInitializationCompleteCallback();
+
     @Inject
     public AccessibilityFloatingMenuController(Context context,
             WindowManager windowManager,
@@ -109,7 +110,8 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             SecureSettings secureSettings,
             DisplayTracker displayTracker,
-            NavigationModeController navigationModeController) {
+            NavigationModeController navigationModeController,
+            Handler handler) {
         mContext = context;
         mWindowManager = windowManager;
         mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
@@ -121,6 +123,7 @@
         mSecureSettings = secureSettings;
         mDisplayTracker = displayTracker;
         mNavigationModeController = navigationModeController;
+        mHandler = handler;
 
         mIsKeyguardVisible = false;
     }
@@ -159,6 +162,8 @@
         mAccessibilityButtonModeObserver.addListener(this);
         mAccessibilityButtonTargetsObserver.addListener(this);
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
+        mAccessibilityManager.registerUserInitializationCompleteCallback(
+                mUserInitializationCompleteCallback);
     }
 
     /**
@@ -172,7 +177,7 @@
      */
     private void handleFloatingMenuVisibility(boolean keyguardVisible,
             @AccessibilityButtonMode int mode, String targets) {
-        if (keyguardVisible) {
+        if (keyguardVisible || mIsUserInInitialization) {
             destroyFloatingMenu();
             return;
         }
@@ -210,4 +215,18 @@
         mFloatingMenu.hide();
         mFloatingMenu = null;
     }
+
+    class UserInitializationCompleteCallback
+            extends IUserInitializationCompleteCallback.Stub {
+        @Override
+        public void onUserInitializationComplete(int userId) {
+            mIsUserInInitialization = false;
+            mContext = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
+            mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
+            mBtnTargets =
+                    mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
+            mHandler.post(
+                    () -> handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets));
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index d718ae3..708f7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -30,8 +30,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Flags;
-import com.android.wm.shell.common.bubbles.DismissCircleView;
-import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.DismissCircleView;
+import com.android.wm.shell.shared.bubbles.DismissView;
 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import java.util.Map;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
index c1b3962..13c1a45 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -39,9 +39,9 @@
 import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
 import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
 import com.android.wm.shell.R
-import com.android.wm.shell.common.bubbles.DismissCircleView
-import com.android.wm.shell.common.bubbles.DismissView
 import com.android.wm.shell.shared.animation.PhysicsAnimator
+import com.android.wm.shell.shared.bubbles.DismissCircleView
+import com.android.wm.shell.shared.bubbles.DismissView
 
 /**
  * View that handles interactions between DismissCircleView and BubbleStackView.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index d62162b..7a674e2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -81,7 +81,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.wm.shell.bubbles.DismissViewUtils;
-import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.shared.bubbles.DismissView;
 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 03f282e..bb80396 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -120,42 +120,42 @@
     @IntoMap
     @StringKey(COLOR_CORRECTION_TILE_SPEC)
     fun provideColorCorrectionAvailabilityInteractor(
-            impl: ColorCorrectionTileDataInteractor
+        impl: ColorCorrectionTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(COLOR_INVERSION_TILE_SPEC)
     fun provideColorInversionAvailabilityInteractor(
-            impl: ColorCorrectionTileDataInteractor
+        impl: ColorCorrectionTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(FONT_SCALING_TILE_SPEC)
     fun provideFontScalingAvailabilityInteractor(
-            impl: FontScalingTileDataInteractor
+        impl: FontScalingTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
     fun provideReduceBrightnessAvailabilityInteractor(
-            impl: ReduceBrightColorsTileDataInteractor
+        impl: ReduceBrightColorsTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(ONE_HANDED_TILE_SPEC)
     fun provideOneHandedAvailabilityInteractor(
-            impl: OneHandedModeTileDataInteractor
+        impl: OneHandedModeTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(NIGHT_DISPLAY_TILE_SPEC)
     fun provideNightDisplayAvailabilityInteractor(
-            impl: NightDisplayTileDataInteractor
+        impl: NightDisplayTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     companion object {
@@ -165,6 +165,7 @@
         const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"
         const val ONE_HANDED_TILE_SPEC = "onehanded"
         const val NIGHT_DISPLAY_TILE_SPEC = "night"
+        const val HEARING_DEVICES_TILE_SPEC = "hearing_devices"
 
         @Provides
         @IntoMap
@@ -273,6 +274,20 @@
                 instanceId = uiEventLogger.getNewInstanceId(),
             )
 
+        @Provides
+        @IntoMap
+        @StringKey(HEARING_DEVICES_TILE_SPEC)
+        fun provideHearingDevicesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(HEARING_DEVICES_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_hearing_devices_icon,
+                        labelRes = R.string.quick_settings_hearing_devices_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
         /**
          * Inject Reduce Bright Colors Tile into tileViewModelMap in QSModule. The tile is hidden
          * behind a flag.
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
index b0314d8..476d54b 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
@@ -32,5 +32,6 @@
 interface AmbientModule {
     companion object {
         const val TOUCH_HANDLERS = "touch_handlers"
+        const val LOGGING_NAME = "logging_name"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
index abdc333..04595a2 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
@@ -48,6 +48,7 @@
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.time.DateFormatUtil;
 
@@ -127,6 +128,9 @@
     private final DreamOverlayStatusBarItemsProvider.Callback mStatusBarItemsProviderCallback =
             this::onStatusBarItemsChanged;
 
+    private final StatusBarWindowStateListener mStatusBarWindowStateListener =
+            this::onSystemStatusBarStateChanged;
+
     @Inject
     public AmbientStatusBarViewController(
             AmbientStatusBarView view,
@@ -161,10 +165,22 @@
         mWifiInteractor = wifiInteractor;
         mCommunalSceneInteractor = communalSceneInteractor;
         mLogger = new DreamLogger(logBuffer, TAG);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
 
         // Register to receive show/hide updates for the system status bar. Our custom status bar
         // needs to hide when the system status bar is showing to ovoid overlapping status bars.
-        statusBarWindowStateController.addListener(this::onSystemStatusBarStateChanged);
+        mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+    }
+
+    @Override
+    public void destroy() {
+        mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
+
+        super.destroy();
     }
 
     @Override
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/ambient/touch/TouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
index 190bc15..d27e72a 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
@@ -122,4 +122,9 @@
      * @param session
      */
     void onSessionStart(TouchSession session);
+
+    /**
+     * Called when the handler is being torn down.
+     */
+    default void onDestroy() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index efa55e9..0898134 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.systemui.ambient.dagger.AmbientModule.LOGGING_NAME;
 import static com.android.systemui.shared.Flags.bouncerAreaExclusion;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
@@ -44,6 +45,9 @@
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 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.CommunalTouchLog;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.util.display.DisplayHelper;
 
@@ -62,6 +66,8 @@
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
+import javax.inject.Named;
+
 
 /**
  * {@link TouchMonitor} is responsible for monitoring touches and gestures over the
@@ -73,6 +79,7 @@
     // This executor is used to protect {@code mActiveTouchSessions} from being modified
     // concurrently. Any operation that adds or removes values should use this executor.
     public String TAG = "DreamOverlayTouchMonitor";
+    private final Logger mLogger;
     private final Executor mMainExecutor;
     private final Executor mBackgroundExecutor;
 
@@ -116,6 +123,11 @@
             TouchSessionImpl touchSessionImpl) {
         return CallbackToFutureAdapter.getFuture(completer -> {
             mMainExecutor.execute(() -> {
+                mLogger.i(msg -> "Session popped, hashCode: " + msg.getInt1(), msg -> {
+                    msg.setInt1(touchSessionImpl.hashCode());
+                    return kotlin.Unit.INSTANCE;
+                });
+
                 if (mActiveTouchSessions.remove(touchSessionImpl)) {
                     touchSessionImpl.onRemoved();
 
@@ -269,6 +281,7 @@
      * When invoked, instantiates a new {@link InputSession} to monitor touch events.
      */
     private void startMonitoring() {
+        mLogger.i("startMonitoring(): monitoring started");
         stopMonitoring(true);
 
         if (bouncerAreaExclusion()) {
@@ -322,6 +335,12 @@
         }
 
         if (!mActiveTouchSessions.isEmpty() && !force) {
+            mLogger.i(msg -> "stopMonitoring(): waiting for sessions to end: " + msg.getStr1(),
+                    msg -> {
+                        msg.setStr1(mActiveTouchSessions.stream().map(Object::hashCode).map(
+                                Object::toString).collect(Collectors.joining(",")));
+                        return kotlin.Unit.INSTANCE;
+                    });
             mStopMonitoringPending = true;
             return;
         }
@@ -341,6 +360,8 @@
         mCurrentInputSession.dispose();
         mCurrentInputSession = null;
         mStopMonitoringPending = false;
+
+        mLogger.i("stopMonitoring(): monitoring finished");
     }
 
 
@@ -405,12 +426,29 @@
                         // created so the
                         // final session is correct.
                         sessionMap.forEach((dreamTouchHandler, touchSession)
-                                -> dreamTouchHandler.onSessionStart(touchSession));
+                                -> {
+                            if (ev instanceof MotionEvent motionEvent) {
+                                int x = Math.round(motionEvent.getX());
+                                int y = Math.round(motionEvent.getY());
+                                mLogger.i(
+                                        msg -> "Session start, handler: " + msg.getStr1() + ", x: "
+                                                + msg.getLong1() + ", y: " + msg.getLong2()
+                                                + ", hashCode: " + msg.getInt1(), msg -> {
+                                            msg.setStr1(
+                                                    dreamTouchHandler.getClass().getSimpleName());
+                                            msg.setLong1(x);
+                                            msg.setLong2(y);
+                                            msg.setInt1(touchSession.hashCode());
+                                            return kotlin.Unit.INSTANCE;
+                                        });
+                            }
+                            dreamTouchHandler.onSessionStart(touchSession);
+                        });
                     }
 
                     // Find active sessions and invoke on InputEvent.
                     mActiveTouchSessions.stream()
-                            .map(touchSessionStack -> touchSessionStack.getEventListeners())
+                            .map(TouchSessionImpl::getEventListeners)
                             .flatMap(Collection::stream)
                             .forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
                 }
@@ -526,6 +564,8 @@
      *                            returned.
      * @param handlers            This set represents the {@link TouchHandler} instances that will
      *                            participate in touch handling.
+     * @param loggingName         Identifying string for this {@link TouchMonitor} that will be used
+     *                            when logging to {@link CommunalTouchLog}.
      */
     @Inject
     public TouchMonitor(
@@ -537,7 +577,9 @@
             ConfigurationInteractor configurationInteractor,
             Set<TouchHandler> handlers,
             IWindowManager windowManagerService,
-            @DisplayId int displayId) {
+            @DisplayId int displayId,
+            @Named(LOGGING_NAME) String loggingName,
+            @CommunalTouchLog LogBuffer logBuffer) {
         mDisplayId = displayId;
         mHandlers = handlers;
         mInputSessionFactory = inputSessionFactory;
@@ -547,6 +589,7 @@
         mDisplayHelper = displayHelper;
         mWindowManagerService = windowManagerService;
         mConfigurationInteractor = configurationInteractor;
+        mLogger = new Logger(logBuffer, loggingName + ":TouchMonitor");
     }
 
     /**
@@ -581,6 +624,10 @@
             mBoundsFlow.cancel(new CancellationException());
         }
 
+        for (TouchHandler handler : mHandlers) {
+            handler.onDestroy();
+        }
+
         mInitialized = false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt
index 390e53b..ba552c3 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.ambient.touch.dagger
 
 import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.ambient.dagger.AmbientModule.Companion.LOGGING_NAME
 import com.android.systemui.ambient.dagger.AmbientModule.Companion.TOUCH_HANDLERS
 import com.android.systemui.ambient.touch.TouchHandler
 import com.android.systemui.ambient.touch.TouchMonitor
@@ -36,7 +37,8 @@
             @BindsInstance lifecycleOwner: LifecycleOwner,
             @BindsInstance
             @Named(TOUCH_HANDLERS)
-            touchHandlers: Set<@JvmSuppressWildcards TouchHandler>
+            touchHandlers: Set<@JvmSuppressWildcards TouchHandler>,
+            @BindsInstance @Named(LOGGING_NAME) loggingName: String,
         ): AmbientTouchComponent
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 468737d..732a90d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -32,11 +32,11 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.onSubscriberAdded
@@ -254,7 +254,7 @@
     override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow()
 
     init {
-        if (SceneContainerFlag.isEnabled) {
+        if (ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) {
             // Hydrate failedAuthenticationAttempts initially and whenever the selected user
             // changes.
             applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index fcba425..3080e19 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.authentication.domain.interactor
 
-import android.app.admin.flags.Flags
 import android.os.UserHandle
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockPatternView
@@ -289,12 +288,7 @@
     private suspend fun getWipeTarget(): WipeTarget {
         // Check which profile has the strictest policy for failed authentication attempts.
         val userToBeWiped = repository.getProfileWithMinFailedUnlockAttemptsForWipe()
-        val primaryUser =
-            if (Flags.headlessSingleUserFixes()) {
-                selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM
-            } else {
-                UserHandle.USER_SYSTEM
-            }
+        val primaryUser = selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM
         return when (userToBeWiped) {
             selectedUserInteractor.getSelectedUserId() ->
                 if (userToBeWiped == primaryUser) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 723587e..970fdea 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
+import static com.android.systemui.Flags.enableViewCaptureTracing;
 
 import android.animation.Animator;
 import android.annotation.IntDef;
@@ -56,6 +57,8 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.app.animation.Interpolators;
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
@@ -75,6 +78,8 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
+import kotlin.Lazy;
+
 import kotlinx.coroutines.CoroutineScope;
 
 import java.io.PrintWriter;
@@ -124,7 +129,7 @@
     private final Config mConfig;
     private final int mEffectiveUserId;
     private final IBinder mWindowToken = new Binder();
-    private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mWindowManager;
     private final Interpolator mLinearOutSlowIn;
     private final LockPatternUtils mLockPatternUtils;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -286,13 +291,16 @@
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull @Background DelayableExecutor bgExecutor,
-            @NonNull VibratorHelper vibratorHelper) {
+            @NonNull VibratorHelper vibratorHelper,
+            Lazy<ViewCapture> lazyViewCapture) {
         super(config.mContext);
 
         mConfig = config;
         mLockPatternUtils = lockPatternUtils;
         mEffectiveUserId = userManager.getCredentialOwnerProfile(mConfig.mUserId);
-        mWindowManager = mContext.getSystemService(WindowManager.class);
+        WindowManager wm = getContext().getSystemService(WindowManager.class);
+        mWindowManager = new ViewCaptureAwareWindowManager(wm, lazyViewCapture,
+                enableViewCaptureTracing());
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mApplicationCoroutineScope = applicationCoroutineScope;
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 037f5b7..097ab72 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -20,6 +20,8 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
 
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
@@ -61,6 +63,7 @@
 import android.view.MotionEvent;
 import android.view.WindowManager;
 
+import com.android.app.viewcapture.ViewCapture;
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -181,6 +184,8 @@
     private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
     @NonNull private final VibratorHelper mVibratorHelper;
 
+    private final kotlin.Lazy<ViewCapture> mLazyViewCapture;
+
     @VisibleForTesting
     final TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
@@ -736,7 +741,8 @@
             @Main Handler handler,
             @Background DelayableExecutor bgExecutor,
             @NonNull UdfpsUtils udfpsUtils,
-            @NonNull VibratorHelper vibratorHelper) {
+            @NonNull VibratorHelper vibratorHelper,
+            Lazy<ViewCapture> daggerLazyViewCapture) {
         mContext = context;
         mExecution = execution;
         mUserManager = userManager;
@@ -785,6 +791,8 @@
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+
+        mLazyViewCapture = toKotlinLazy(daggerLazyViewCapture);
     }
 
     // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
@@ -1318,7 +1326,8 @@
         return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps,
                 wakefulnessLifecycle, userManager, lockPatternUtils,
                 mInteractionJankMonitor, mPromptSelectorInteractor, viewModel,
-                mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
+                mCredentialViewModelProvider, bgExecutor, mVibratorHelper,
+                mLazyViewCapture);
     }
 
     @Override
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/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 25d43d9..4c2fe07 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -941,16 +941,16 @@
     private fun vibrateOnSuccess() {
         _hapticsToPlay.value =
             HapticsToPlay(
-                HapticFeedbackConstants.CONFIRM,
-                HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+                HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+                null,
             )
     }
 
     private fun vibrateOnError() {
         _hapticsToPlay.value =
             HapticsToPlay(
-                HapticFeedbackConstants.REJECT,
-                HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
+                HapticFeedbackConstants.BIOMETRIC_REJECT,
+                null,
             )
     }
 
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/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
index 094dc0a..e939f37 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerMessageRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bouncer.data.repository
 
+import android.hardware.biometrics.BiometricSourceType
 import com.android.systemui.bouncer.shared.model.BouncerMessageModel
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
@@ -26,7 +27,12 @@
 interface BouncerMessageRepository {
     val bouncerMessage: Flow<BouncerMessageModel>
 
-    fun setMessage(message: BouncerMessageModel)
+    fun setMessage(message: BouncerMessageModel, source: BiometricSourceType? = null)
+
+    /**
+     * Return the source of the current [bouncerMessage] if it was set from a biometric. Else, null.
+     */
+    fun getMessageSource(): BiometricSourceType?
 }
 
 @SysUISingleton
@@ -34,8 +40,14 @@
 
     private val _bouncerMessage = MutableStateFlow(BouncerMessageModel())
     override val bouncerMessage: Flow<BouncerMessageModel> = _bouncerMessage
+    private var messageSource: BiometricSourceType? = null
 
-    override fun setMessage(message: BouncerMessageModel) {
+    override fun setMessage(message: BouncerMessageModel, source: BiometricSourceType?) {
         _bouncerMessage.value = message
+        messageSource = source
+    }
+
+    override fun getMessageSource(): BiometricSourceType? {
+        return messageSource
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index 8e12646..d125c36 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bouncer.domain.interactor
 
+import android.hardware.biometrics.BiometricFaceConstants
 import android.hardware.biometrics.BiometricSourceType
 import android.os.CountDownTimer
 import com.android.keyguard.KeyguardSecurityModel
@@ -32,9 +33,7 @@
 import com.android.systemui.bouncer.shared.model.Message
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
 import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
@@ -74,8 +73,6 @@
     primaryBouncerInteractor: PrimaryBouncerInteractor,
     @Application private val applicationScope: CoroutineScope,
     private val facePropertyRepository: FacePropertyRepository,
-    private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
-    faceAuthRepository: DeviceEntryFaceAuthRepository,
     private val securityModel: KeyguardSecurityModel,
     deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
 ) {
@@ -96,6 +93,17 @@
     private val kumCallback =
         object : KeyguardUpdateMonitorCallback() {
             override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+                // Only show the biometric failure messages if the biometric is NOT locked out.
+                // If the biometric is locked out, rely on the lock out message to show
+                // the lockout message & don't override it with the failure message.
+                if (
+                    (biometricSourceType == BiometricSourceType.FACE &&
+                        deviceEntryBiometricsAllowedInteractor.isFaceLockedOut.value) ||
+                        (biometricSourceType == BiometricSourceType.FINGERPRINT &&
+                            deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut.value)
+                ) {
+                    return
+                }
                 repository.setMessage(
                     when (biometricSourceType) {
                         BiometricSourceType.FINGERPRINT ->
@@ -115,7 +123,8 @@
                                     isFingerprintAuthCurrentlyAllowedOnBouncer.value
                                 )
                                 .toMessage()
-                    }
+                    },
+                    biometricSourceType,
                 )
             }
 
@@ -123,7 +132,12 @@
                 biometricSourceType: BiometricSourceType?,
                 acquireInfo: Int
             ) {
-                super.onBiometricAcquired(biometricSourceType, acquireInfo)
+                if (
+                    repository.getMessageSource() == BiometricSourceType.FACE &&
+                        acquireInfo == BiometricFaceConstants.FACE_ACQUIRED_START
+                ) {
+                    repository.setMessage(defaultMessage)
+                }
             }
 
             override fun onBiometricAuthenticated(
@@ -131,7 +145,7 @@
                 biometricSourceType: BiometricSourceType?,
                 isStrongBiometric: Boolean
             ) {
-                repository.setMessage(defaultMessage)
+                repository.setMessage(defaultMessage, biometricSourceType)
             }
         }
 
@@ -152,8 +166,8 @@
                 biometricSettingsRepository.authenticationFlags,
                 trustRepository.isCurrentUserTrustManaged,
                 isAnyBiometricsEnabledAndEnrolled,
-                deviceEntryFingerprintAuthInteractor.isLockedOut,
-                faceAuthRepository.isLockedOut,
+                deviceEntryBiometricsAllowedInteractor.isFingerprintLockedOut,
+                deviceEntryBiometricsAllowedInteractor.isFaceLockedOut,
                 isFingerprintAuthCurrentlyAllowedOnBouncer,
                 ::Septuple
             )
@@ -291,7 +305,8 @@
                 currentSecurityMode,
                 value,
                 isFingerprintAuthCurrentlyAllowedOnBouncer.value
-            )
+            ),
+            BiometricSourceType.FINGERPRINT,
         )
     }
 
@@ -302,7 +317,8 @@
                 currentSecurityMode,
                 value,
                 isFingerprintAuthCurrentlyAllowedOnBouncer.value
-            )
+            ),
+            BiometricSourceType.FACE,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
index 62ef365..a1111f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -17,18 +17,17 @@
 package com.android.systemui.bouncer.shared.flag
 
 import com.android.systemui.Flags
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import dagger.Module
-import dagger.Provides
 
-interface ComposeBouncerFlags {
+object ComposeBouncerFlags {
 
     /**
      * Returns `true` if the Compose bouncer is enabled or if the scene container framework is
      * enabled; `false` otherwise.
      */
-    fun isComposeBouncerOrSceneContainerEnabled(): Boolean
+    fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
+        return SceneContainerFlag.isEnabled || Flags.composeBouncer()
+    }
 
     /**
      * Returns `true` if only compose bouncer is enabled and scene container framework is not
@@ -39,30 +38,7 @@
             "that includes compose bouncer in legacy keyguard.",
         replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
     )
-    fun isOnlyComposeBouncerEnabled(): Boolean
-}
-
-class ComposeBouncerFlagsImpl() : ComposeBouncerFlags {
-
-    override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
-        return SceneContainerFlag.isEnabled || Flags.composeBouncer()
-    }
-
-    @Deprecated(
-        "Avoid using this, this is meant to be used only by the glue code " +
-            "that includes compose bouncer in legacy keyguard.",
-        replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
-    )
-    override fun isOnlyComposeBouncerEnabled(): Boolean {
+    fun isOnlyComposeBouncerEnabled(): Boolean {
         return !SceneContainerFlag.isEnabled && Flags.composeBouncer()
     }
 }
-
-@Module
-object ComposeBouncerFlagsModule {
-    @Provides
-    @SysUISingleton
-    fun impl(): ComposeBouncerFlags {
-        return ComposeBouncerFlagsImpl()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index ad93a25..49dadce 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -2,13 +2,11 @@
 
 import android.view.ViewGroup
 import com.android.keyguard.KeyguardMessageAreaController
-import com.android.keyguard.ViewMediatorCallback
 import com.android.keyguard.dagger.KeyguardBouncerComponent
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerContainerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.dagger.SysUISingleton
@@ -39,12 +37,9 @@
 data class ComposeBouncerDependencies
 @Inject
 constructor(
-    val legacyInteractor: PrimaryBouncerInteractor,
     val viewModelFactory: BouncerSceneContentViewModel.Factory,
     val dialogFactory: BouncerDialogFactory,
-    val authenticationInteractor: AuthenticationInteractor,
-    val viewMediatorCallback: ViewMediatorCallback?,
-    val selectedUserInteractor: SelectedUserInteractor,
+    val bouncerContainerViewModelFactory: BouncerContainerViewModel.Factory,
 )
 
 /**
@@ -55,21 +50,17 @@
 class BouncerViewBinder
 @Inject
 constructor(
-    private val composeBouncerFlags: ComposeBouncerFlags,
     private val legacyBouncerDependencies: Lazy<LegacyBouncerDependencies>,
     private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>,
 ) {
     fun bind(view: ViewGroup) {
-        if (composeBouncerFlags.isOnlyComposeBouncerEnabled()) {
+        if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
             val deps = composeBouncerDependencies.get()
             ComposeBouncerViewBinder.bind(
                 view,
-                deps.legacyInteractor,
                 deps.viewModelFactory,
                 deps.dialogFactory,
-                deps.authenticationInteractor,
-                deps.selectedUserInteractor,
-                deps.viewMediatorCallback,
+                deps.bouncerContainerViewModelFactory,
             )
         } else {
             val deps = legacyBouncerDependencies.get()
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..b5e54d5 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
@@ -5,98 +5,55 @@
 import androidx.activity.OnBackPressedDispatcherOwner
 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
 import androidx.compose.ui.platform.ComposeView
-import androidx.core.view.isVisible
+import androidx.core.view.isGone
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.compose.theme.PlatformTheme
-import com.android.keyguard.ViewMediatorCallback
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.composable.BouncerContent
+import com.android.systemui.bouncer.ui.composable.BouncerContainer
+import com.android.systemui.bouncer.ui.viewmodel.BouncerContainerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
-import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import kotlinx.coroutines.awaitCancellation
 
 /** View binder responsible for binding the compose version of the bouncer. */
 object ComposeBouncerViewBinder {
     fun bind(
         view: ViewGroup,
-        legacyInteractor: PrimaryBouncerInteractor,
         viewModelFactory: BouncerSceneContentViewModel.Factory,
         dialogFactory: BouncerDialogFactory,
-        authenticationInteractor: AuthenticationInteractor,
-        selectedUserInteractor: SelectedUserInteractor,
-        viewMediatorCallback: ViewMediatorCallback?,
+        bouncerContainerViewModelFactory: BouncerContainerViewModel.Factory,
     ) {
-        view.addView(
-            ComposeView(view.context).apply {
-                repeatWhenAttached {
-                    repeatOnLifecycle(Lifecycle.State.CREATED) {
-                        setViewTreeOnBackPressedDispatcherOwner(
-                            object : OnBackPressedDispatcherOwner {
-                                override val onBackPressedDispatcher =
-                                    OnBackPressedDispatcher().apply {
-                                        setOnBackInvokedDispatcher(
-                                            view.viewRootImpl.onBackInvokedDispatcher
-                                        )
-                                    }
-
-                                override val lifecycle: Lifecycle =
-                                    this@repeatWhenAttached.lifecycle
-                            }
-                        )
-                        setContent {
-                            PlatformTheme {
-                                BouncerContent(
-                                    rememberViewModel { viewModelFactory.create() },
-                                    dialogFactory,
-                                )
-                            }
-                        }
-                    }
-                }
-            }
-        )
-
         view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch {
-                    legacyInteractor.isShowing.collectLatest { bouncerShowing ->
-                        view.isVisible = bouncerShowing
-                    }
-                }
-
-                launch {
-                    authenticationInteractor.onAuthenticationResult.collectLatest {
-                        authenticationSucceeded ->
-                        if (authenticationSucceeded) {
-                            // Some dismiss actions require that keyguard be dismissed right away or
-                            // deferred until something else later on dismisses keyguard (eg. end of
-                            // a hide animation).
-                            val deferKeyguardDone =
-                                legacyInteractor.bouncerDismissAction?.onDismissAction?.onDismiss()
-                            legacyInteractor.setDismissAction(null, null)
-
-                            viewMediatorCallback?.let {
-                                val selectedUserId = selectedUserInteractor.getSelectedUserId()
-                                if (deferKeyguardDone == true) {
-                                    it.keyguardDonePending(selectedUserId)
-                                } else {
-                                    it.keyguardDone(selectedUserId)
+            view.viewModel(
+                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+                factory = { bouncerContainerViewModelFactory.create() },
+                traceName = "ComposeBouncerViewBinder",
+            ) { viewModel ->
+                try {
+                    view.setViewTreeOnBackPressedDispatcherOwner(
+                        object : OnBackPressedDispatcherOwner {
+                            override val onBackPressedDispatcher =
+                                OnBackPressedDispatcher().apply {
+                                    setOnBackInvokedDispatcher(
+                                        view.viewRootImpl.onBackInvokedDispatcher
+                                    )
                                 }
-                            }
+
+                            override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
                         }
-                    }
-                }
-                launch {
-                    legacyInteractor.startingDisappearAnimation.collectLatest {
-                        it.run()
-                        legacyInteractor.hide()
-                    }
+                    )
+
+                    view.addView(
+                        ComposeView(view.context).apply {
+                            setContent { BouncerContainer(viewModelFactory, dialogFactory) }
+                        }
+                    )
+                    view.setSnapshotBinding { view.isGone = !viewModel.isVisible }
+                    awaitCancellation()
+                } finally {
+                    view.removeAllViews()
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt
new file mode 100644
index 0000000..c05dcd5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/composable/BouncerContainer.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.bouncer.ui.composable
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.bouncer.ui.BouncerDialogFactory
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.rememberViewModel
+
+/** Container that includes the compose bouncer and is meant to be included in legacy keyguard. */
+@Composable
+fun BouncerContainer(
+    viewModelFactory: BouncerSceneContentViewModel.Factory,
+    dialogFactory: BouncerDialogFactory,
+) {
+    PlatformTheme {
+        val backgroundColor = MaterialTheme.colorScheme.surface
+
+        val bouncerViewModel = rememberViewModel("BouncerContainer") { viewModelFactory.create() }
+        Box {
+            Canvas(Modifier.fillMaxSize()) { drawRect(color = backgroundColor) }
+
+            // Separate the bouncer content into a reusable composable that
+            // doesn't have any SceneScope
+            // dependencies
+            BouncerContent(
+                bouncerViewModel,
+                dialogFactory,
+                Modifier.sysuiResTag(Bouncer.TestTags.Root).fillMaxSize()
+            )
+        }
+    }
+}
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/BouncerContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt
new file mode 100644
index 0000000..d223657
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerContainerViewModel.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.bouncer.ui.viewmodel
+
+import androidx.compose.runtime.getValue
+import com.android.keyguard.ViewMediatorCallback
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+class BouncerContainerViewModel
+@AssistedInject
+constructor(
+    private val legacyInteractor: PrimaryBouncerInteractor,
+    private val authenticationInteractor: AuthenticationInteractor,
+    private val selectedUserInteractor: SelectedUserInteractor,
+    private val viewMediatorCallback: ViewMediatorCallback?,
+) : ExclusiveActivatable() {
+
+    private val hydrator = Hydrator("BouncerContainerViewModel")
+
+    val isVisible: Boolean by
+        hydrator.hydratedStateOf(traceName = "isVisible", source = legacyInteractor.isShowing)
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch {
+                authenticationInteractor.onAuthenticationResult.collect { authenticationSucceeded ->
+                    if (authenticationSucceeded) {
+                        // Some dismiss actions require that keyguard be dismissed right away or
+                        // deferred until something else later on dismisses keyguard (eg. end of
+                        // a hide animation).
+                        val deferKeyguardDone =
+                            legacyInteractor.bouncerDismissAction?.onDismissAction?.onDismiss()
+                        legacyInteractor.setDismissAction(null, null)
+
+                        viewMediatorCallback?.let {
+                            val selectedUserId = selectedUserInteractor.getSelectedUserId()
+                            if (deferKeyguardDone == true) {
+                                it.keyguardDonePending(selectedUserId)
+                            } else {
+                                it.keyguardDone(selectedUserId)
+                            }
+                        }
+                    }
+                }
+            }
+
+            launch {
+                legacyInteractor.startingDisappearAnimation.collect {
+                    it.run()
+                    legacyInteractor.hide()
+                }
+            }
+
+            hydrator.activate()
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): BouncerContainerViewModel
+    }
+}
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..c383b8d 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
@@ -78,8 +78,7 @@
     private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
     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.
@@ -96,7 +95,7 @@
     val message: MutableStateFlow<MessageViewModel?> = MutableStateFlow(null)
 
     override suspend fun onActivated(): Nothing {
-        if (!flags.isComposeBouncerOrSceneContainerEnabled()) {
+        if (!ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) {
             return awaitCancellation()
         }
 
@@ -155,7 +154,7 @@
                     emptyFlow()
                 }
             }
-            .collectLatest { messageViewModel -> message.value = messageViewModel }
+            .collect { messageViewModel -> message.value = messageViewModel }
     }
 
     private suspend fun listenForSimBouncerEvents() {
@@ -170,7 +169,7 @@
                     emptyFlow()
                 }
             }
-            .collectLatest {
+            .collect {
                 if (it != null) {
                     message.value = it
                 } else {
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..0aada06 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,17 +23,17 @@
 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
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
 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
@@ -56,13 +56,12 @@
     private val authenticationInteractor: AuthenticationInteractor,
     private val devicePolicyManager: DevicePolicyManager,
     private val bouncerMessageViewModelFactory: BouncerMessageViewModel.Factory,
-    private val flags: ComposeBouncerFlags,
     private val userSwitcher: UserSwitcherViewModel,
     private val actionButtonInteractor: BouncerActionButtonInteractor,
     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 +145,7 @@
                     .map(::getChildViewModel)
                     .collectLatest { childViewModelOrNull ->
                         _authMethodViewModel.value = childViewModelOrNull
-                        childViewModelOrNull?.activate()
+                        childViewModelOrNull?.let { traceCoroutine(it.traceName) { it.activate() } }
                     }
             }
 
@@ -159,7 +158,7 @@
             launch {
                 userSwitcher.selectedUser
                     .map { it.image.toBitmap() }
-                    .collectLatest { _selectedUserImage.value = it }
+                    .collect { _selectedUserImage.value = it }
             }
 
             launch {
@@ -186,34 +185,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/BouncerSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt
index 2a27271..4fe6fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerUserActionsViewModel.kt
@@ -22,21 +22,20 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
 
 /**
  * Models UI state for user actions that can lead to navigation to other scenes when showing the
  * bouncer scene.
  */
-class BouncerSceneActionsViewModel
+class BouncerUserActionsViewModel
 @AssistedInject
 constructor(
     private val bouncerInteractor: BouncerInteractor,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
         bouncerInteractor.dismissDestination
@@ -46,11 +45,11 @@
                     Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
                 )
             }
-            .collectLatest { actions -> setActions(actions) }
+            .collect { actions -> setActions(actions) }
     }
 
     @AssistedFactory
     interface Factory {
-        fun create(): BouncerSceneActionsViewModel
+        fun create(): BouncerUserActionsViewModel
     }
 }
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/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index d2caefd..e1ba93c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -76,7 +76,6 @@
     private final boolean mTestHarness;
     private final MetricsLogger mMetricsLogger;
     private int mIsFalseTouchCalls;
-    private FeatureFlags mFeatureFlags;
     private static final Queue<String> RECENT_INFO_LOG =
             new ArrayDeque<>(RECENT_INFO_LOG_SIZE + 1);
     private static final Queue<DebugSwipeRecord> RECENT_SWIPES =
@@ -186,8 +185,7 @@
             DoubleTapClassifier doubleTapClassifier, HistoryTracker historyTracker,
             KeyguardStateController keyguardStateController,
             AccessibilityManager accessibilityManager,
-            @TestHarness boolean testHarness,
-            FeatureFlags featureFlags) {
+            @TestHarness boolean testHarness) {
         mDataProvider = falsingDataProvider;
         mMetricsLogger = metricsLogger;
         mClassifiers = classifiers;
@@ -198,7 +196,6 @@
         mKeyguardStateController = keyguardStateController;
         mAccessibilityManager = accessibilityManager;
         mTestHarness = testHarness;
-        mFeatureFlags = featureFlags;
 
         mDataProvider.addSessionListener(mSessionListener);
         mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
@@ -399,8 +396,7 @@
                 || mDataProvider.isA11yAction()
                 || mDataProvider.isFromTrackpad()
                 || mDataProvider.isFromKeyboard()
-                || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
-                    && mDataProvider.isUnfolded());
+                || mDataProvider.isUnfolded();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 040af90..aabfbd1 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -20,6 +20,7 @@
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
 import static com.android.systemui.Flags.clipboardImageTimeout;
+import static com.android.systemui.Flags.clipboardSharedTransitions;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -33,7 +34,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -207,7 +207,7 @@
         mClipboardUtils = clipboardUtils;
         mBgExecutor = bgExecutor;
 
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             mView.setCallbacks(this);
         } else {
             mView.setCallbacks(mClipboardCallbacks);
@@ -220,7 +220,7 @@
         });
 
         mTimeoutHandler.setOnTimeoutRunnable(() -> {
-            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+            if (clipboardSharedTransitions()) {
                 finish(CLIPBOARD_OVERLAY_TIMED_OUT);
             } else {
                 mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
@@ -232,7 +232,7 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
-                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                    if (clipboardSharedTransitions()) {
                         finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                     } else {
                         mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -248,7 +248,7 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (SCREENSHOT_ACTION.equals(intent.getAction())) {
-                    if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                    if (clipboardSharedTransitions()) {
                         finish(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
                     } else {
                         mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
@@ -481,7 +481,7 @@
                 remoteAction.ifPresent(action -> {
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
                     mView.post(() -> mView.setActionChip(action, () -> {
-                        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                        if (clipboardSharedTransitions()) {
                             finish(CLIPBOARD_OVERLAY_ACTION_TAPPED);
                         } else {
                             mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
@@ -528,7 +528,7 @@
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
                         if (!mView.isInTouchRegion(
                                 (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
-                            if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+                            if (clipboardSharedTransitions()) {
                                 finish(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
                             } else {
                                 mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
@@ -575,6 +575,10 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
+                // check again after animation to see if we should still be minimized
+                if (mIsMinimized && !shouldShowMinimized(mWindow.getWindowInsets())) {
+                    animateFromMinimized();
+                }
                 if (mOnUiUpdate != null) {
                     mOnUiUpdate.run();
                 }
@@ -690,14 +694,14 @@
 
     @Override
     public void onDismissButtonTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             finish(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
         }
     }
 
     @Override
     public void onRemoteCopyButtonTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             finish(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED,
                     IntentCreator.getRemoteCopyIntent(mClipboardModel.getClipData(), mContext));
         }
@@ -705,7 +709,7 @@
 
     @Override
     public void onShareButtonTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             if (mClipboardModel.getType() != ClipboardModel.Type.OTHER) {
                 finishWithSharedTransition(CLIPBOARD_OVERLAY_SHARE_TAPPED,
                         IntentCreator.getShareIntent(mClipboardModel.getClipData(), mContext));
@@ -715,7 +719,7 @@
 
     @Override
     public void onPreviewTapped() {
-        if (mFeatureFlags.isEnabled(CLIPBOARD_SHARED_TRANSITIONS)) {
+        if (clipboardSharedTransitions()) {
             switch (mClipboardModel.getType()) {
                 case TEXT:
                     finish(CLIPBOARD_OVERLAY_EDIT_TAPPED,
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
index 0dc6fda..dc3b50c 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
@@ -27,6 +27,7 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.policy.PhoneWindow;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
 import com.android.systemui.screenshot.FloatingWindowUtil;
@@ -44,6 +45,7 @@
 
     private final Context mContext;
     private final WindowManager mWindowManager;
+    private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
     private final WindowManager.LayoutParams mWindowLayoutParams;
 
     private boolean mKeyboardVisible;
@@ -52,7 +54,9 @@
     private Runnable mOnOrientationChangeListener;
 
     @Inject
-    ClipboardOverlayWindow(@OverlayWindowContext Context context) {
+    ClipboardOverlayWindow(@OverlayWindowContext Context context,
+            @OverlayWindowContext ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+            @OverlayWindowContext WindowManager windowManager) {
         super(context);
         mContext = context;
         mOrientation = mContext.getResources().getConfiguration().orientation;
@@ -61,10 +65,11 @@
         requestFeature(Window.FEATURE_NO_TITLE);
         requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
         setBackgroundDrawableResource(android.R.color.transparent);
-        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mWindowManager = windowManager;
+        mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
         mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
         mWindowLayoutParams.setTitle("ClipboardOverlay");
-        setWindowManager(mWindowManager, null, null);
+        setWindowManager(windowManager, null, null);
         setWindowFocusable(false);
     }
 
@@ -81,10 +86,12 @@
 
         attach();
         withWindowAttached(() -> {
-            WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+            WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics()
+                    .getWindowInsets();
             mKeyboardVisible = currentInsets.isVisible(WindowInsets.Type.ime());
             peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(() -> {
-                WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+                WindowInsets insets = mWindowManager.getCurrentWindowMetrics()
+                        .getWindowInsets();
                 boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
                 if (keyboardVisible != mKeyboardVisible) {
                     mKeyboardVisible = keyboardVisible;
@@ -105,7 +112,7 @@
     void remove() {
         final View decorView = peekDecorView();
         if (decorView != null && decorView.isAttachedToWindow()) {
-            mWindowManager.removeViewImmediate(decorView);
+            mViewCaptureAwareWindowManager.removeViewImmediate(decorView);
         }
     }
 
@@ -139,7 +146,7 @@
         if (decorView.isAttachedToWindow()) {
             return;
         }
-        mWindowManager.addView(decorView, mWindowLayoutParams);
+        mViewCaptureAwareWindowManager.addView(decorView, mWindowLayoutParams);
         decorView.requestApplyInsets();
     }
 
@@ -160,7 +167,7 @@
         }
         final View decorView = peekDecorView();
         if (decorView != null && decorView.isAttachedToWindow()) {
-            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+            mViewCaptureAwareWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
         }
     }
 }
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/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
index ff9fba4..307a07f 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -18,17 +18,24 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
+import static com.android.systemui.Flags.enableViewCaptureTracing;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
+
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.view.Display;
 import android.view.LayoutInflater;
+import android.view.WindowManager;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.clipboardoverlay.ClipboardOverlayView;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 
+import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -61,6 +68,28 @@
                 R.layout.clipboard_overlay, null);
     }
 
+    /**
+     *
+     */
+    @Provides
+    @OverlayWindowContext
+    static WindowManager provideWindowManager(@OverlayWindowContext Context context) {
+        return context.getSystemService(WindowManager.class);
+    }
+
+    /**
+     *
+     */
+    @Provides
+    @OverlayWindowContext
+    static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager(
+            @OverlayWindowContext WindowManager windowManager,
+            Lazy<ViewCapture> daggerLazyViewCapture) {
+        return new ViewCaptureAwareWindowManager(windowManager,
+                /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture),
+                /* isViewCaptureEnabled= */ enableViewCaptureTracing());
+    }
+
     @Qualifier
     @Documented
     @Retention(RUNTIME)
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/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
index 3cdb573..aef5f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
@@ -38,5 +38,5 @@
 }
 
 /** Creates [Icon.Loaded] for a given drawable with an optional [contentDescription]. */
-fun Drawable.asIcon(contentDescription: ContentDescription? = null): Icon =
+fun Drawable.asIcon(contentDescription: ContentDescription? = null): Icon.Loaded =
     Icon.Loaded(this, contentDescription)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
index 578389b..13f6bba 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -23,35 +23,25 @@
 import androidx.annotation.DimenRes
 import androidx.annotation.LayoutRes
 import com.android.settingslib.Utils
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
 import com.android.systemui.statusbar.policy.onThemeChanged
 import com.android.systemui.util.kotlin.emitOnStart
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
-/** Configuration-aware-state-tracking utilities. */
-class ConfigurationState
-@Inject
-constructor(
-    private val configurationController: ConfigurationController,
-    @Application private val context: Context,
-    private val layoutInflater: LayoutInflater,
-) {
+interface ConfigurationState {
     /**
      * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
      * configuration.
      *
      * @see android.content.res.Resources.getDimensionPixelSize
      */
-    fun getDimensionPixelSize(@DimenRes id: Int): Flow<Int> {
-        return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
-            context.resources.getDimensionPixelSize(id)
-        }
-    }
+    fun getDimensionPixelSize(@DimenRes id: Int): Flow<Int>
 
     /**
      * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
@@ -59,22 +49,14 @@
      *
      * @see android.content.res.Resources.getDimensionPixelSize
      */
-    fun getDimensionPixelOffset(@DimenRes id: Int): Flow<Int> {
-        return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
-            context.resources.getDimensionPixelOffset(id)
-        }
-    }
+    fun getDimensionPixelOffset(@DimenRes id: Int): Flow<Int>
 
     /**
      * Returns a [Flow] that emits a color that is kept in sync with the device theme.
      *
      * @see Utils.getColorAttrDefaultColor
      */
-    fun getColorAttr(@AttrRes id: Int, @ColorInt defaultValue: Int): Flow<Int> {
-        return configurationController.onThemeChanged.emitOnStart().map {
-            Utils.getColorAttrDefaultColor(context, id, defaultValue)
-        }
-    }
+    fun getColorAttr(@AttrRes id: Int, @ColorInt defaultValue: Int): Flow<Int>
 
     /**
      * Returns a [Flow] that emits a [View] that is re-inflated as necessary to remain in sync with
@@ -87,6 +69,65 @@
         @LayoutRes id: Int,
         root: ViewGroup?,
         attachToRoot: Boolean,
+    ): Flow<T>
+}
+
+/** Configuration-aware-state-tracking utilities. */
+class ConfigurationStateImpl
+@AssistedInject
+constructor(
+    @Assisted private val configurationController: ConfigurationController,
+    @Assisted private val context: Context,
+) : ConfigurationState {
+
+    private val layoutInflater = LayoutInflater.from(context)
+
+    /**
+     * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
+     * configuration.
+     *
+     * @see android.content.res.Resources.getDimensionPixelSize
+     */
+    override fun getDimensionPixelSize(@DimenRes id: Int): Flow<Int> {
+        return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
+            context.resources.getDimensionPixelSize(id)
+        }
+    }
+
+    /**
+     * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
+     * configuration.
+     *
+     * @see android.content.res.Resources.getDimensionPixelSize
+     */
+    override fun getDimensionPixelOffset(@DimenRes id: Int): Flow<Int> {
+        return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
+            context.resources.getDimensionPixelOffset(id)
+        }
+    }
+
+    /**
+     * Returns a [Flow] that emits a color that is kept in sync with the device theme.
+     *
+     * @see Utils.getColorAttrDefaultColor
+     */
+    override fun getColorAttr(@AttrRes id: Int, @ColorInt defaultValue: Int): Flow<Int> {
+        return configurationController.onThemeChanged.emitOnStart().map {
+            Utils.getColorAttrDefaultColor(context, id, defaultValue)
+        }
+    }
+
+    /**
+     * Returns a [Flow] that emits a [View] that is re-inflated as necessary to remain in sync with
+     * the device configuration.
+     *
+     * @see LayoutInflater.inflate
+     */
+    @Suppress("UNCHECKED_CAST")
+    override fun <T : View> inflateLayout(
+        @LayoutRes id: Int,
+        root: ViewGroup?,
+        attachToRoot: Boolean,
     ): Flow<T> {
         // TODO(b/305930747): This may lead to duplicate invocations if both flows emit, find a
         //  solution to only emit one event.
@@ -97,4 +138,16 @@
             .emitOnStart()
             .map { layoutInflater.inflate(id, root, attachToRoot) as T }
     }
+
+    @AssistedFactory
+    interface Factory {
+        /**
+         * Creates a configurationState for a given context. The [configurationController] is
+         * supposed to give config events specific for that context.
+         */
+        fun create(
+            context: Context,
+            configurationController: ConfigurationController
+        ): ConfigurationStateImpl
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
new file mode 100644
index 0000000..b36da3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.common.ui
+
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
+
+/**
+ * Annotates elements that provide information from the global configuration.
+ *
+ * The global configuration is the one associted with the main display. Secondary displays will
+ * apply override to the global configuration. Elements annotated with this shouldn't be used for
+ * secondary displays.
+ */
+@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig
+
+@Module
+interface ConfigurationStateModule {
+
+    /**
+     * Deprecated: [ConfigurationState] should be injected only with the correct annotation. For
+     * now, without annotation the global config associated state is provided.
+     */
+    @Binds
+    fun provideGlobalConfigurationState(
+        @GlobalConfig configurationState: ConfigurationState
+    ): ConfigurationState
+
+    companion object {
+        @SysUISingleton
+        @Provides
+        @GlobalConfig
+        fun provideGlobalConfigurationState(
+            configStateFactory: ConfigurationStateImpl.Factory,
+            configurationController: ConfigurationController,
+            @Application context: Context,
+        ): ConfigurationState {
+            return configStateFactory.create(context, configurationController)
+        }
+    }
+}
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/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index dff6352..8f1854f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -26,7 +26,7 @@
 import androidx.sqlite.db.SupportSQLiteDatabase
 import com.android.systemui.res.R
 
-@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 2)
+@Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 3)
 abstract class CommunalDatabase : RoomDatabase() {
     abstract fun communalWidgetDao(): CommunalWidgetDao
 
@@ -55,7 +55,7 @@
                             context.resources.getString(R.string.config_communalDatabase)
                         )
                         .also { builder ->
-                            builder.addMigrations(MIGRATION_1_2)
+                            builder.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                             builder.fallbackToDestructiveMigration(dropAllTables = true)
                             callback?.let { callback -> builder.addCallback(callback) }
                         }
@@ -87,5 +87,21 @@
                     )
                 }
             }
+
+        /**
+         * This migration reverses the ranks. For example, if the ranks are 2, 1, 0, then after the
+         * migration they will be 0, 1, 2.
+         */
+        @VisibleForTesting
+        val MIGRATION_2_3 =
+            object : Migration(2, 3) {
+                override fun migrate(db: SupportSQLiteDatabase) {
+                    Log.i(TAG, "Migrating from version 2 to 3")
+                    db.execSQL(
+                        "UPDATE communal_item_rank_table " +
+                            "SET rank = (SELECT MAX(rank) FROM communal_item_rank_table) - rank"
+                    )
+                }
+            }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 933a25a..93b86bd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -97,7 +97,7 @@
                             .addWidget(
                                 widgetId = id,
                                 componentName = name,
-                                priority = defaultWidgets.size - index,
+                                rank = index,
                                 userSerialNumber = userSerialNumber,
                             )
                     }
@@ -132,10 +132,17 @@
     @Query(
         "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
             "ON communal_item_rank_table.uid = communal_widget_table.item_id " +
-            "ORDER BY communal_item_rank_table.rank DESC"
+            "ORDER BY communal_item_rank_table.rank ASC"
     )
     fun getWidgets(): Flow<Map<CommunalItemRank, CommunalWidgetItem>>
 
+    @Query(
+        "SELECT * FROM communal_widget_table JOIN communal_item_rank_table " +
+            "ON communal_item_rank_table.uid = communal_widget_table.item_id " +
+            "ORDER BY communal_item_rank_table.rank ASC"
+    )
+    fun getWidgetsNow(): Map<CommunalItemRank, CommunalWidgetItem>
+
     @Query("SELECT * FROM communal_widget_table WHERE widget_id = :id")
     fun getWidgetByIdNow(id: Int): CommunalWidgetItem?
 
@@ -167,11 +174,11 @@
     @Query("DELETE FROM communal_item_rank_table") fun clearCommunalItemRankTable()
 
     @Transaction
-    fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
-        widgetIdToPriorityMap.forEach { (id, priority) ->
+    fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
+        widgetIdToRankMap.forEach { (id, rank) ->
             val widget = getWidgetByIdNow(id)
             if (widget != null) {
-                updateItemRank(widget.itemId, priority)
+                updateItemRank(widget.itemId, rank)
             }
         }
     }
@@ -180,13 +187,13 @@
     fun addWidget(
         widgetId: Int,
         provider: ComponentName,
-        priority: Int,
+        rank: Int? = null,
         userSerialNumber: Int,
     ): Long {
         return addWidget(
             widgetId = widgetId,
             componentName = provider.flattenToString(),
-            priority = priority,
+            rank = rank,
             userSerialNumber = userSerialNumber,
         )
     }
@@ -195,13 +202,27 @@
     fun addWidget(
         widgetId: Int,
         componentName: String,
-        priority: Int,
+        rank: Int? = null,
         userSerialNumber: Int,
     ): Long {
+        val widgets = getWidgetsNow()
+
+        // If rank is not specified, rank it last by finding the current maximum rank and increment
+        // by 1. If the new widget is the first widget, set the rank to 0.
+        val newRank = rank ?: widgets.keys.maxOfOrNull { it.rank + 1 } ?: 0
+
+        // Shift widgets after [rank], unless widget is added at the end.
+        if (rank != null) {
+            widgets.forEach { (rankEntry, widgetEntry) ->
+                if (rankEntry.rank < newRank) return@forEach
+                updateItemRank(widgetEntry.itemId, rankEntry.rank + 1)
+            }
+        }
+
         return insertWidget(
             widgetId = widgetId,
             componentName = componentName,
-            itemId = insertItemRank(priority),
+            itemId = insertItemRank(newRank),
             userSerialNumber = userSerialNumber,
         )
     }
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/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ad0bfc7..6cdd9ff 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -57,12 +57,17 @@
     /** A flow of information about active communal widgets stored in database. */
     val communalWidgets: Flow<List<CommunalWidgetContentModel>>
 
-    /** Add a widget at the specified position in the app widget service and the database. */
+    /**
+     * Add a widget in the app widget service and the database.
+     *
+     * @param rank The rank of the widget determines its position in the grid. 0 is first place, 1
+     *   is second, etc. If rank is not specified, widget is added at the end.
+     */
     fun addWidget(
         provider: ComponentName,
         user: UserHandle,
-        priority: Int,
-        configurator: WidgetConfigurator? = null
+        rank: Int?,
+        configurator: WidgetConfigurator? = null,
     ) {}
 
     /**
@@ -75,9 +80,9 @@
     /**
      * Update the order of widgets in the database.
      *
-     * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
+     * @param widgetIdToRankMap mapping of the widget ids to the rank of the widget.
      */
-    fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
+    fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {}
 
     /**
      * Restores the database by reading a state file from disk and updating the widget ids according
@@ -121,7 +126,7 @@
                 CommunalWidgetEntry(
                     appWidgetId = widget.widgetId,
                     componentName = widget.componentName,
-                    priority = rank.rank,
+                    rank = rank.rank,
                     providerInfo = providers[widget.widgetId]
                 )
             }
@@ -151,8 +156,8 @@
     override fun addWidget(
         provider: ComponentName,
         user: UserHandle,
-        priority: Int,
-        configurator: WidgetConfigurator?
+        rank: Int?,
+        configurator: WidgetConfigurator?,
     ) {
         bgScope.launch {
             val id = communalWidgetHost.allocateIdAndBindWidget(provider, user)
@@ -190,14 +195,14 @@
                 communalWidgetDao.addWidget(
                     widgetId = id,
                     provider = provider,
-                    priority = priority,
+                    rank = rank,
                     userSerialNumber = userManager.getUserSerialNumber(user.identifier),
                 )
                 backupManager.dataChanged()
             } else {
                 appWidgetHost.deleteAppWidgetId(id)
             }
-            logger.i("Added widget ${provider.flattenToString()} at position $priority.")
+            logger.i("Added widget ${provider.flattenToString()} at position $rank.")
         }
     }
 
@@ -211,11 +216,11 @@
         }
     }
 
-    override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
+    override fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
         bgScope.launch {
-            communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
+            communalWidgetDao.updateWidgetOrder(widgetIdToRankMap)
             logger.i({ "Updated the order of widget list with ids: $str1." }) {
-                str1 = widgetIdToPriorityMap.toString()
+                str1 = widgetIdToRankMap.toString()
             }
             backupManager.dataChanged()
         }
@@ -342,7 +347,7 @@
                 addWidget(
                     provider = ComponentName.unflattenFromString(widget.componentName)!!,
                     user = newUser,
-                    priority = widget.rank,
+                    rank = widget.rank,
                 )
             }
 
@@ -377,7 +382,7 @@
         return CommunalWidgetContentModel.Available(
             appWidgetId = entry.appWidgetId,
             providerInfo = entry.providerInfo!!,
-            priority = entry.priority,
+            rank = entry.rank,
         )
     }
 
@@ -394,7 +399,7 @@
             return CommunalWidgetContentModel.Available(
                 appWidgetId = entry.appWidgetId,
                 providerInfo = entry.providerInfo!!,
-                priority = entry.priority,
+                rank = entry.rank,
             )
         }
 
@@ -403,7 +408,7 @@
         return if (componentName != null && session != null) {
             CommunalWidgetContentModel.Pending(
                 appWidgetId = entry.appWidgetId,
-                priority = entry.priority,
+                rank = entry.rank,
                 componentName = componentName,
                 icon = session.icon,
                 user = session.user,
@@ -416,7 +421,7 @@
     private data class CommunalWidgetEntry(
         val appWidgetId: Int,
         val componentName: String,
-        val priority: Int,
+        val rank: Int,
         var providerInfo: AppWidgetProviderInfo? = null,
     )
 }
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 7181b15..b570e14 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
@@ -61,6 +61,7 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.ManagedProfileController
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.emitOnStart
@@ -116,6 +117,7 @@
     sceneInteractor: SceneInteractor,
     @CommunalLog logBuffer: LogBuffer,
     @CommunalTableLog tableLogBuffer: TableLogBuffer,
+    private val managedProfileController: ManagedProfileController
 ) {
     private val logger = Logger(logBuffer, "CommunalInteractor")
 
@@ -140,6 +142,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 +185,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 +355,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)
     }
 
     /**
@@ -367,13 +376,16 @@
     /** Dismiss the CTA tile from the hub in view mode. */
     suspend fun dismissCtaTile() = communalPrefsInteractor.setCtaDismissed()
 
-    /** Add a widget at the specified position. */
+    /**
+     * Add a widget at the specified rank. If rank is not provided, the widget will be added at the
+     * end.
+     */
     fun addWidget(
         componentName: ComponentName,
         user: UserHandle,
-        priority: Int,
+        rank: Int? = null,
         configurator: WidgetConfigurator?,
-    ) = widgetRepository.addWidget(componentName, user, priority, configurator)
+    ) = widgetRepository.addWidget(componentName, user, rank, configurator)
 
     /**
      * Delete a widget by id. Called when user deletes a widget from the hub or a widget is
@@ -384,19 +396,14 @@
     /**
      * Reorder the widgets.
      *
-     * @param widgetIdToPriorityMap mapping of the widget ids to their new priorities.
+     * @param widgetIdToRankMap mapping of the widget ids to their new priorities.
      */
-    fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
-        widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
+    fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) =
+        widgetRepository.updateWidgetOrder(widgetIdToRankMap)
 
     /** Request to unpause work profile that is currently in quiet mode. */
     fun unpauseWorkProfile() {
-        userTracker.userProfiles
-            .find { it.isManagedProfile }
-            ?.userHandle
-            ?.let { userHandle ->
-                userManager.requestQuietModeEnabled(/* enableQuietMode */ false, userHandle)
-            }
+        managedProfileController.setWorkModeEnabled(true)
     }
 
     /** Returns true if work profile is in quiet mode (disabled) for user handle. */
@@ -440,7 +447,7 @@
                     is CommunalWidgetContentModel.Available -> {
                         WidgetContent.Widget(
                             appWidgetId = widget.appWidgetId,
-                            priority = widget.priority,
+                            rank = widget.rank,
                             providerInfo = widget.providerInfo,
                             appWidgetHost = appWidgetHost,
                             inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
@@ -449,7 +456,7 @@
                     is CommunalWidgetContentModel.Pending -> {
                         WidgetContent.PendingWidget(
                             appWidgetId = widget.appWidgetId,
-                            priority = widget.priority,
+                            rank = widget.rank,
                             componentName = widget.componentName,
                             icon = widget.icon,
                         )
@@ -500,7 +507,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>()
 
@@ -516,7 +523,7 @@
                 )
 
                 // Add UMO
-                if (mediaHostVisible && media.hasAnyMediaOrRecommendation) {
+                if (media.hasAnyMediaOrRecommendation) {
                     ongoingContent.add(
                         CommunalContentModel.Umo(
                             createdTimestampMillis = media.createdTimestampMillis,
@@ -604,11 +611,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..ac496f0 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,
@@ -193,7 +195,7 @@
                     is ObservableTransitionState.Idle ->
                         flowOf(CommunalTransitionProgressModel.Idle(state.currentScene))
                     is ObservableTransitionState.Transition ->
-                        if (state.toScene == targetScene) {
+                        if (state.toContent == targetScene) {
                             state.progress.map {
                                 CommunalTransitionProgressModel.Transition(
                                     // Clamp the progress values between 0 and 1 as actual progress
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 6343752..c7538bb4 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
@@ -161,7 +161,7 @@
         if (
             prevTransition is ObservableTransitionState.Transition &&
                 currentTransitionId != null &&
-                idle.currentScene == prevTransition.toScene
+                idle.currentScene == prevTransition.toContent
         ) {
             finishCurrentTransition()
         } else {
@@ -182,6 +182,7 @@
     }
 
     private suspend fun finishCurrentTransition() {
+        if (currentTransitionId == null) return
         internalTransitionInteractor.updateTransition(
             currentTransitionId!!,
             1f,
@@ -218,24 +219,22 @@
         prevTransition: ObservableTransitionState,
         transition: ObservableTransitionState.Transition
     ) {
-        if (prevTransition.isTransitioning(from = transition.fromScene, to = transition.toScene)) {
+        if (
+            prevTransition.isTransitioning(from = transition.fromContent, to = transition.toContent)
+        ) {
             // This is a new transition, but exactly the same as the previous state. Skip resetting
             // KTF for this case and just collect the new progress instead.
             collectProgress(transition)
-        } else if (transition.toScene == CommunalScenes.Communal) {
-            if (currentTransitionId != null) {
-                if (currentToState == KeyguardState.GLANCEABLE_HUB) {
-                    transitionKtfTo(transitionInteractor.getStartedFromState())
-                }
+        } else if (transition.toContent == CommunalScenes.Communal) {
+            if (currentToState == KeyguardState.GLANCEABLE_HUB) {
+                transitionKtfTo(transitionInteractor.startedKeyguardTransitionStep.value.from)
             }
             startTransitionToGlanceableHub()
             collectProgress(transition)
-        } else if (transition.toScene == CommunalScenes.Blank) {
-            if (currentTransitionId != null) {
-                // Another transition started before this one is completed. Transition to the
-                // GLANCEABLE_HUB state so that we can properly transition away from it.
-                transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
-            }
+        } else if (transition.toContent == CommunalScenes.Blank) {
+            // Another transition started before this one is completed. Transition to the
+            // GLANCEABLE_HUB state so that we can properly transition away from it.
+            transitionKtfTo(KeyguardState.GLANCEABLE_HUB)
             startTransitionFromGlanceableHub()
             collectProgress(transition)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 73c6ce3..4c821d4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -47,12 +47,12 @@
 
     sealed interface WidgetContent : CommunalContentModel {
         val appWidgetId: Int
-        val priority: Int
+        val rank: Int
         val componentName: ComponentName
 
         data class Widget(
             override val appWidgetId: Int,
-            override val priority: Int,
+            override val rank: Int,
             val providerInfo: AppWidgetProviderInfo,
             val appWidgetHost: CommunalAppWidgetHost,
             val inQuietMode: Boolean,
@@ -71,7 +71,7 @@
 
         data class DisabledWidget(
             override val appWidgetId: Int,
-            override val priority: Int,
+            override val rank: Int,
             val providerInfo: AppWidgetProviderInfo
         ) : WidgetContent {
             override val key = KEY.disabledWidget(appWidgetId)
@@ -85,7 +85,7 @@
 
         data class PendingWidget(
             override val appWidgetId: Int,
-            override val priority: Int,
+            override val rank: Int,
             override val componentName: ComponentName,
             val icon: Bitmap? = null,
         ) : WidgetContent {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
index 2352841f..1def5a3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt
@@ -126,13 +126,13 @@
 /** Whether currently transitioning from another scene to communal. */
 private fun ObservableTransitionState.isSwipingToCommunal(): Boolean {
     return this is ObservableTransitionState.Transition &&
-        toScene == CommunalScenes.Communal &&
+        toContent == CommunalScenes.Communal &&
         isInitiatedByUserInput
 }
 
 /** Whether currently transitioning from communal to another scene. */
 private fun ObservableTransitionState.isSwipingFromCommunal(): Boolean {
     return this is ObservableTransitionState.Transition &&
-        fromScene == CommunalScenes.Communal &&
+        fromContent == CommunalScenes.Communal &&
         isInitiatedByUserInput
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
index 9ce8cf7..7cfad60 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -31,7 +31,7 @@
     private val statsLogProxy: StatsLogProxy,
 ) {
     /** Logs an add widget event for metrics. No-op if widget is not loggable. */
-    fun logAddWidget(componentName: String, rank: Int) {
+    fun logAddWidget(componentName: String, rank: Int?) {
         if (!componentName.isLoggable()) {
             return
         }
@@ -39,7 +39,7 @@
         statsLogProxy.writeCommunalHubWidgetEventReported(
             SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
             componentName,
-            rank,
+            rank ?: -1,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt
index aed9215..83f31e5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt
@@ -74,8 +74,8 @@
                     tag = TAG,
                     level = LogLevel.INFO,
                     messageInitializer = {
-                        str1 = transitionState.fromScene.toString()
-                        str2 = transitionState.toScene.toString()
+                        str1 = transitionState.fromContent.toString()
+                        str2 = transitionState.toContent.toString()
                     },
                     messagePrinter = { "Scene transition started: $str1 → $str2" },
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 7cddb72..63b1a14 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -24,19 +24,19 @@
 /** Encapsulates data for a communal widget. */
 sealed interface CommunalWidgetContentModel {
     val appWidgetId: Int
-    val priority: Int
+    val rank: Int
 
     /** Widget is ready to display */
     data class Available(
         override val appWidgetId: Int,
         val providerInfo: AppWidgetProviderInfo,
-        override val priority: Int,
+        override val rank: Int,
     ) : CommunalWidgetContentModel
 
     /** Widget is pending installation */
     data class Pending(
         override val appWidgetId: Int,
-        override val priority: Int,
+        override val rank: Int,
         val componentName: ComponentName,
         val icon: Bitmap?,
         val user: UserHandle,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 80db535..012c844 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -65,6 +65,12 @@
     var preconditionListener =
         object : SmartspacePrecondition.Listener {
             override fun onCriteriaChanged() {
+                if (session == null && hasActiveSessionListeners()) {
+                    Log.d(TAG, "Precondition criteria changed. Attempting to connect session.")
+                    connectSession()
+                    return
+                }
+
                 reloadSmartspace()
             }
         }
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 b822133..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)
 
@@ -153,7 +150,7 @@
     open fun onAddWidget(
         componentName: ComponentName,
         user: UserHandle,
-        priority: Int,
+        rank: Int? = null,
         configurator: WidgetConfigurator? = null,
     ) {}
 
@@ -161,23 +158,23 @@
     open fun onDeleteWidget(
         id: Int,
         componentName: ComponentName,
-        priority: Int,
+        rank: Int,
     ) {}
 
     /** Called as the UI detects a tap event on the widget. */
     open fun onTapWidget(
         componentName: ComponentName,
-        priority: Int,
+        rank: Int,
     ) {}
 
     /**
      * Called as the UI requests reordering widgets.
      *
-     * @param widgetIdToPriorityMap mapping of the widget ids to its priority. When re-ordering to
-     *   add a new item in the middle, provide the priorities of existing widgets as if the new item
-     *   existed, and then, call [onAddWidget] to add the new item at intended order.
+     * @param widgetIdToRankMap mapping of the widget ids to its rank. When re-ordering to add a new
+     *   item in the middle, provide the priorities of existing widgets as if the new item existed,
+     *   and then, call [onAddWidget] to add the new item at intended order.
      */
-    open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
+    open fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) {}
 
     /** Called as the UI requests opening the widget editor with an optional preselected widget. */
     open fun onOpenWidgetEditor(
@@ -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 1a86c71..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")
@@ -125,24 +127,24 @@
     override fun onAddWidget(
         componentName: ComponentName,
         user: UserHandle,
-        priority: Int,
+        rank: Int?,
         configurator: WidgetConfigurator?
     ) {
-        communalInteractor.addWidget(componentName, user, priority, configurator)
-        metricsLogger.logAddWidget(componentName.flattenToString(), priority)
+        communalInteractor.addWidget(componentName, user, rank, configurator)
+        metricsLogger.logAddWidget(componentName.flattenToString(), rank)
     }
 
     override fun onDeleteWidget(
         id: Int,
         componentName: ComponentName,
-        priority: Int,
+        rank: Int,
     ) {
         communalInteractor.deleteWidget(id)
-        metricsLogger.logRemoveWidget(componentName.flattenToString(), priority)
+        metricsLogger.logRemoveWidget(componentName.flattenToString(), rank)
     }
 
-    override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
-        communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
+    override fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) =
+        communalInteractor.updateWidgetOrder(widgetIdToRankMap)
 
     override fun onReorderWidgetStart() {
         // Clear selection status
@@ -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 c0a18f2..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() {
@@ -272,8 +285,8 @@
         }
     }
 
-    override fun onTapWidget(componentName: ComponentName, priority: Int) {
-        metricsLogger.logTapWidget(componentName.flattenToString(), priority)
+    override fun onTapWidget(componentName: ComponentName, rank: Int) {
+        metricsLogger.logTapWidget(componentName.flattenToString(), rank)
     }
 
     fun onClick() {
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 b421e59..d84dc20 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.communal.widgets
 
+import android.app.Activity
+import android.app.Application.ActivityLifecycleCallbacks
 import android.content.Intent
+import android.content.IntentSender
 import android.os.Bundle
 import android.os.RemoteException
 import android.util.Log
@@ -34,6 +37,7 @@
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.Flags.communalEditWidgetsActivityFinishFix
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -64,16 +68,109 @@
     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"
     }
 
+    /**
+     * [ActivityController] handles closing the activity in the case it is backgrounded without
+     * waiting for an activity result
+     */
+    interface ActivityController {
+        /**
+         * Invoked when waiting for an activity result changes, either initiating such wait or
+         * finishing due to the return of a result.
+         */
+        fun onWaitingForResult(waitingForResult: Boolean) {}
+
+        /** Set the visibility of the activity under control. */
+        fun setActivityFullyVisible(fullyVisible: Boolean) {}
+    }
+
+    /**
+     * A nop ActivityController to be use when the communalEditWidgetsActivityFinishFix flag is
+     * false.
+     */
+    class NopActivityController : ActivityController
+
+    /**
+     * A functional ActivityController to be used when the communalEditWidgetsActivityFinishFix flag
+     * is true.
+     */
+    class ActivityControllerImpl(activity: Activity) : ActivityController {
+        companion object {
+            private const val STATE_EXTRA_IS_WAITING_FOR_RESULT = "extra_is_waiting_for_result"
+        }
+
+        private var waitingForResult = false
+        private var activityFullyVisible = false
+
+        init {
+            activity.registerActivityLifecycleCallbacks(
+                object : ActivityLifecycleCallbacks {
+                    override fun onActivityCreated(
+                        activity: Activity,
+                        savedInstanceState: Bundle?
+                    ) {
+                        waitingForResult =
+                            savedInstanceState?.getBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT)
+                                ?: false
+                    }
+
+                    override fun onActivityStarted(activity: Activity) {
+                        // Nothing to implement.
+                    }
+
+                    override fun onActivityResumed(activity: Activity) {
+                        // Nothing to implement.
+                    }
+
+                    override fun onActivityPaused(activity: Activity) {
+                        // Nothing to implement.
+                    }
+
+                    override fun onActivityStopped(activity: Activity) {
+                        // If we're not backgrounded due to waiting for a result (either widget
+                        // selection or configuration), and we are fully visible, then finish the
+                        // activity.
+                        if (
+                            !waitingForResult &&
+                                activityFullyVisible &&
+                                !activity.isChangingConfigurations
+                        ) {
+                            activity.finish()
+                        }
+                    }
+
+                    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
+                        outState.putBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT, waitingForResult)
+                    }
+
+                    override fun onActivityDestroyed(activity: Activity) {
+                        // Nothing to implement.
+                    }
+                }
+            )
+        }
+
+        override fun onWaitingForResult(waitingForResult: Boolean) {
+            this.waitingForResult = waitingForResult
+        }
+
+        override fun setActivityFullyVisible(fullyVisible: Boolean) {
+            activityFullyVisible = fullyVisible
+        }
+    }
+
     private val logger = Logger(logBuffer, "EditWidgetsActivity")
 
     private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
 
     private var shouldOpenWidgetPickerOnStart = false
 
+    private val activityController: ActivityController =
+        if (communalEditWidgetsActivityFinishFix()) ActivityControllerImpl(this)
+        else NopActivityController()
+
     private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
         registerForActivityResult(StartActivityForResult()) { result ->
             when (result.resultCode) {
@@ -89,11 +186,11 @@
                         if (!isPendingWidgetDrag) {
                             val (componentName, user) = getWidgetExtraFromIntent(intent)
                             if (componentName != null && user != null) {
+                                // Add widget at the end.
                                 communalViewModel.onAddWidget(
                                     componentName,
                                     user,
-                                    0,
-                                    widgetConfigurator
+                                    configurator = widgetConfigurator,
                                 )
                             } else {
                                 run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
@@ -111,20 +208,19 @@
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+
         listenForTransitionAndChangeScene()
 
+        activityController.setActivityFullyVisible(false)
         communalViewModel.setEditModeOpen(true)
 
         val windowInsetsController = window.decorView.windowInsetsController
         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(
@@ -159,6 +255,9 @@
                 communalViewModel.currentScene.first { it == CommunalScenes.Blank }
                 communalViewModel.setEditModeState(EditModeState.SHOWING)
 
+                // Inform the ActivityController that we are now fully visible.
+                activityController.setActivityFullyVisible(true)
+
                 // Show the widget picker, if necessary, after the edit activity has animated in.
                 // Waiting until after the activity has appeared avoids transitions issues.
                 if (shouldOpenWidgetPickerOnStart) {
@@ -171,11 +270,7 @@
 
     private fun onOpenWidgetPicker() {
         lifecycleScope.launch {
-            communalViewModel.onOpenWidgetPicker(
-                resources,
-                packageManager,
-                addWidgetActivityLauncher
-            )
+            communalViewModel.onOpenWidgetPicker(resources, addWidgetActivityLauncher)
         }
     }
 
@@ -198,7 +293,34 @@
         }
     }
 
+    override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {
+        activityController.onWaitingForResult(true)
+        super.startActivityForResult(intent, requestCode, options)
+    }
+
+    override fun startIntentSenderForResult(
+        intent: IntentSender,
+        requestCode: Int,
+        fillInIntent: Intent?,
+        flagsMask: Int,
+        flagsValues: Int,
+        extraFlags: Int,
+        options: Bundle?
+    ) {
+        activityController.onWaitingForResult(true)
+        super.startIntentSenderForResult(
+            intent,
+            requestCode,
+            fillInIntent,
+            flagsMask,
+            flagsValues,
+            extraFlags,
+            options
+        )
+    }
+
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        activityController.onWaitingForResult(false)
         super.onActivityResult(requestCode, resultCode, data)
         if (requestCode == WidgetConfigurationController.REQUEST_CODE) {
             widgetConfigurator.setConfigurationResult(resultCode)
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/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 1dd3722..3fe6669 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -21,6 +21,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactoryBase;
+import com.android.systemui.common.ui.GlobalConfig;
 import com.android.systemui.dagger.qualifiers.PerUser;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
@@ -127,6 +128,7 @@
      * Creates a ContextComponentHelper.
      */
     @SysUISingleton
+    @GlobalConfig
     ConfigurationController getConfigurationController();
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 0363a68..b55108d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,6 +48,7 @@
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.common.data.CommonDataLayerModule;
+import com.android.systemui.common.ui.ConfigurationStateModule;
 import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule;
 import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -66,6 +67,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;
@@ -206,6 +208,7 @@
         ClockRegistryModule.class,
         CommunalModule.class,
         CommonDataLayerModule.class,
+        ConfigurationStateModule.class,
         CommonUsageStatsDataLayerModule.class,
         ConfigurationControllerModule.class,
         ConnectivityModule.class,
@@ -231,6 +234,7 @@
         MediaProjectionTaskSwitcherModule.class,
         MediaRouterModule.class,
         MotionToolModule.class,
+        MSDLModule.class,
         PeopleHubModule.class,
         PeopleModule.class,
         PluginModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 9460eaf..37c6e17 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -57,7 +57,10 @@
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.power.domain.interactor.PowerInteractor
+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.scene.shared.model.Scenes.Bouncer
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
@@ -159,6 +162,7 @@
     private val powerInteractor: PowerInteractor,
     private val keyguardInteractor: KeyguardInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val sceneInteractor: dagger.Lazy<SceneInteractor>,
     @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -385,7 +389,18 @@
                 biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
                 "isFaceAuthEnrolledAndEnabled"
             ),
-            Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
+            Pair(
+                if (SceneContainerFlag.isEnabled) {
+                    sceneInteractor
+                        .get()
+                        .transitionState
+                        .map { it.isTransitioning(to = Scenes.Gone) || it.isIdle(Scenes.Gone) }
+                        .isFalse()
+                } else {
+                    keyguardRepository.isKeyguardGoingAway.isFalse()
+                },
+                "keyguardNotGoingAway"
+            ),
             Pair(
                 keyguardTransitionInteractor
                     .isInTransitionWhere(toStatePredicate = KeyguardState::deviceIsAsleepInState)
@@ -397,7 +412,11 @@
                     .isFalse()
                     .or(
                         alternateBouncerInteractor.isVisible.or(
-                            keyguardInteractor.primaryBouncerShowing
+                            if (SceneContainerFlag.isEnabled) {
+                                sceneInteractor.get().transitionState.map { it.isIdle(Bouncer) }
+                            } else {
+                                keyguardInteractor.primaryBouncerShowing
+                            }
                         )
                     ),
                 "secureCameraNotActiveOrAnyBouncerIsShowing"
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt
index 79b176c..7aee12f 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricsAllowedInteractor.kt
@@ -22,6 +22,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
@@ -44,18 +45,30 @@
     biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
     facePropertyRepository: FacePropertyRepository,
 ) {
+    /**
+     * Whether face is locked out due to too many failed face attempts. This currently includes
+     * whether face is not allowed based on other biometric lockouts; however does not include if
+     * face isn't allowed due to other strong or primary authentication requirements.
+     */
+    val isFaceLockedOut: StateFlow<Boolean> = deviceEntryFaceAuthInteractor.isLockedOut
 
     private val isStrongFaceAuth: Flow<Boolean> =
         facePropertyRepository.sensorInfo.map { it?.strength == SensorStrength.STRONG }
 
     private val isStrongFaceAuthLockedOut: Flow<Boolean> =
-        combine(isStrongFaceAuth, deviceEntryFaceAuthInteractor.isLockedOut) {
-            isStrongFaceAuth,
-            isFaceAuthLockedOut ->
+        combine(isStrongFaceAuth, isFaceLockedOut) { isStrongFaceAuth, isFaceAuthLockedOut ->
             isStrongFaceAuth && isFaceAuthLockedOut
         }
 
     /**
+     * Whether fingerprint is locked out due to too many failed fingerprint attempts. This does NOT
+     * include whether fingerprint is not allowed based on other biometric lockouts nor if
+     * fingerprint isn't allowed due to other strong or primary authentication requirements.
+     */
+    val isFingerprintLockedOut: StateFlow<Boolean> =
+        deviceEntryFingerprintAuthInteractor.isLockedOut
+
+    /**
      * Whether fingerprint authentication is currently allowed for the user. This is true if the
      * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
      * [com.android.systemui.keyguard.shared.model.AuthenticationFlags], not locked out due to too
@@ -64,7 +77,7 @@
      */
     val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> =
         combine(
-            deviceEntryFingerprintAuthInteractor.isLockedOut,
+            isFingerprintLockedOut,
             biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed,
             isStrongFaceAuthLockedOut,
         ) { fpLockedOut, fpAllowedBySettings, strongAuthFaceAuthLockedOut ->
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index dff391a..cdd2b05 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -60,14 +60,23 @@
     fun unregisterListener(listener: FaceAuthenticationListener)
 
     fun onUdfpsSensorTouched()
+
     fun onAssistantTriggeredOnLockScreen()
+
     fun onDeviceLifted()
-    fun onQsExpansionStared()
+
+    fun onShadeExpansionStarted()
+
     fun onNotificationPanelClicked()
+
     fun onSwipeUpOnBouncer()
+
     fun onPrimaryBouncerUserInput()
+
     fun onAccessibilityAction()
+
     fun onWalletLaunched()
+
     fun onDeviceUnfolded()
 
     /** Whether face auth is considered class 3 */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index 969f53f..5c058fe 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -54,7 +54,7 @@
     val authenticationStatus: Flow<FingerprintAuthenticationStatus> =
         repository.authenticationStatus
 
-    val isLockedOut: Flow<Boolean> = repository.isLockedOut
+    val isLockedOut: StateFlow<Boolean> = repository.isLockedOut
 
     val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> =
         repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>()
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 39f4e31..7018f9d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
+import com.android.internal.policy.IKeyguardDismissCallback
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
+import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.utils.coroutines.flow.mapLatestConflated
@@ -56,6 +58,7 @@
     private val sceneInteractor: SceneInteractor,
     private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val dismissCallbackRegistry: DismissCallbackRegistry,
 ) {
     /**
      * Whether the device is unlocked.
@@ -126,17 +129,14 @@
                 },
                 isLockscreenEnabled,
                 deviceUnlockedInteractor.deviceUnlockStatus,
-                isDeviceEntered) {
-                    isNoneAuthMethod,
-                    isLockscreenEnabled,
-                    deviceUnlockStatus,
-                    isDeviceEntered ->
-                    val isSwipeAuthMethod = isNoneAuthMethod && isLockscreenEnabled
-                    (isSwipeAuthMethod ||
-                        (deviceUnlockStatus.isUnlocked &&
-                            deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) &&
-                        !isDeviceEntered
-                }
+                isDeviceEntered
+            ) { isNoneAuthMethod, isLockscreenEnabled, deviceUnlockStatus, isDeviceEntered ->
+                val isSwipeAuthMethod = isNoneAuthMethod && isLockscreenEnabled
+                (isSwipeAuthMethod ||
+                    (deviceUnlockStatus.isUnlocked &&
+                        deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) &&
+                    !isDeviceEntered
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
@@ -150,8 +150,16 @@
     /**
      * Attempt to enter the device and dismiss the lockscreen. If authentication is required to
      * unlock the device it will transition to bouncer.
+     *
+     * @param callback An optional callback to invoke when the attempt succeeds, fails, or is
+     *   canceled
      */
-    fun attemptDeviceEntry() {
+    @JvmOverloads
+    fun attemptDeviceEntry(
+        callback: IKeyguardDismissCallback? = null,
+    ) {
+        callback?.let { dismissCallbackRegistry.addCallback(it) }
+
         // TODO (b/307768356),
         //       1. Check if the device is already authenticated by trust agent/passive biometrics
         //       2. Show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index de5d0aa..9b8c2b1 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -47,6 +47,7 @@
     override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
 
     override fun isFaceAuthStrong(): Boolean = false
+
     override fun start() = Unit
 
     override fun registerListener(listener: FaceAuthenticationListener) {}
@@ -59,13 +60,17 @@
 
     override fun onDeviceLifted() {}
 
-    override fun onQsExpansionStared() {}
+    override fun onShadeExpansionStarted() {}
 
     override fun onNotificationPanelClicked() {}
 
     override fun onSwipeUpOnBouncer() {}
+
     override fun onPrimaryBouncerUserInput() {}
+
     override fun onAccessibilityAction() {}
+
     override fun onWalletLaunched() = Unit
+
     override fun onDeviceUnfolded() {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index c536d6b..3b5d5a8 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -46,6 +46,9 @@
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
@@ -57,6 +60,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
@@ -90,6 +94,7 @@
     private val powerInteractor: PowerInteractor,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val trustManager: TrustManager,
+    private val sceneInteractor: Lazy<SceneInteractor>,
     deviceEntryFaceAuthStatusInteractor: DeviceEntryFaceAuthStatusInteractor,
 ) : DeviceEntryFaceAuthInteractor {
 
@@ -103,9 +108,7 @@
         keyguardUpdateMonitor.setFaceAuthInteractor(this)
         observeFaceAuthStateUpdates()
         faceAuthenticationLogger.interactorStarted()
-        primaryBouncerInteractor
-            .get()
-            .isShowing
+        isBouncerVisible
             .whenItFlipsToTrue()
             .onEach {
                 faceAuthenticationLogger.bouncerVisibilityChanged()
@@ -181,19 +184,23 @@
         // auth so that the switched user can unlock the device with face auth.
         userRepository.selectedUser
             .pairwise()
-            .onEach { (previous, curr) ->
+            .filter { (previous, curr) ->
                 val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
-                if (wasSwitching && !isSwitching) {
-                    resetLockedOutState(curr.userInfo.id)
-                    yield()
-                    runFaceAuth(
-                        FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
-                        // Fallback to detection if bouncer is not showing so that we can detect a
-                        // face and then show the bouncer to the user if face auth can't run
-                        fallbackToDetect = !primaryBouncerInteractor.get().isBouncerShowing()
-                    )
-                }
+                // User switching was in progress and is complete now.
+                wasSwitching && !isSwitching
+            }
+            .map { (_, curr) -> curr.userInfo.id }
+            .sample(isBouncerVisible, ::Pair)
+            .onEach { (userId, isBouncerCurrentlyVisible) ->
+                resetLockedOutState(userId)
+                yield()
+                runFaceAuth(
+                    FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
+                    // Fallback to detection if bouncer is not showing so that we can detect a
+                    // face and then show the bouncer to the user if face auth can't run
+                    fallbackToDetect = !isBouncerCurrentlyVisible
+                )
             }
             .launchIn(applicationScope)
 
@@ -208,6 +215,24 @@
                 }
             }
             .launchIn(applicationScope)
+
+        if (SceneContainerFlag.isEnabled) {
+            sceneInteractor
+                .get()
+                .transitionState
+                .filter { it.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade) }
+                .distinctUntilChanged()
+                .onEach { onShadeExpansionStarted() }
+                .launchIn(applicationScope)
+        }
+    }
+
+    private val isBouncerVisible: Flow<Boolean> by lazy {
+        if (SceneContainerFlag.isEnabled) {
+            sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) }
+        } else {
+            primaryBouncerInteractor.get().isShowing
+        }
     }
 
     private suspend fun resetLockedOutState(currentUserId: Int) {
@@ -225,8 +250,8 @@
         runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
     }
 
-    override fun onQsExpansionStared() {
-        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)
+    override fun onShadeExpansionStarted() {
+        runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, false)
     }
 
     override fun onDeviceLifted() {
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 40e2f17..1f5878b 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -48,6 +48,7 @@
 import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.scan
 import kotlinx.coroutines.flow.shareIn
@@ -184,6 +185,9 @@
         if (Flags.enableEfficientDisplayRepository()) {
             enabledDisplayIds
                 .mapElementsLazily { displayId -> getDisplay(displayId) }
+                .onEach {
+                    if (it.isEmpty()) Log.wtf(TAG, "No enabled displays. This should never happen.")
+                }
                 .flowOn(backgroundCoroutineDispatcher)
                 .debugLog("enabledDisplays")
                 .stateIn(
@@ -194,7 +198,8 @@
                     // performance concerns.
                     // Ultimately, this is a trade-off between a one-time UI thread binder call and
                     // the constant overhead of sharedFlows.
-                    initialValue = getDisplays())
+                    initialValue = getDisplays()
+                )
         } else {
             oldEnabledDisplays
         }
@@ -380,9 +385,8 @@
             val resultSet: Set<V>
         )
 
-        val initialState = State(emptySet<T>(), emptyMap(), emptySet<V>())
-
-        return this.scan(initialState) { state, currentSet ->
+        val emptyInitialState = State(emptySet<T>(), emptyMap(), emptySet<V>())
+        return this.scan(emptyInitialState) { state, currentSet ->
                 if (currentSet == state.previousSet) {
                     state
                 } else {
@@ -397,6 +401,7 @@
                     State(currentSet, newMap, resultSet)
                 }
             }
+            .filter { it != emptyInitialState }
             .map { it.resultSet }
     }
 
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 b45ebd8..9051745 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.CrossFadeHelper
 import javax.inject.Inject
 import javax.inject.Named
+import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.launch
 
 /** Controller for dream overlay animations. */
@@ -84,51 +85,62 @@
 
     private var mCurrentBlurRadius: Float = 0f
 
+    private var mLifecycleFlowHandle: DisposableHandle? = null
+
     fun init(view: View) {
         this.view = view
 
-        view.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                launch {
-                    dreamViewModel.dreamOverlayTranslationY.collect { px ->
-                        ComplicationLayoutParams.iteratePositions(
-                            { position: Int -> setElementsTranslationYAtPosition(px, position) },
-                            POSITION_TOP or POSITION_BOTTOM
-                        )
+        mLifecycleFlowHandle =
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch {
+                        dreamViewModel.dreamOverlayTranslationY.collect { px ->
+                            ComplicationLayoutParams.iteratePositions(
+                                { position: Int ->
+                                    setElementsTranslationYAtPosition(px, position)
+                                },
+                                POSITION_TOP or POSITION_BOTTOM
+                            )
+                        }
                     }
-                }
 
-                launch {
-                    dreamViewModel.dreamOverlayTranslationX.collect { px ->
-                        ComplicationLayoutParams.iteratePositions(
-                            { position: Int -> setElementsTranslationXAtPosition(px, position) },
-                            POSITION_TOP or POSITION_BOTTOM
-                        )
+                    launch {
+                        dreamViewModel.dreamOverlayTranslationX.collect { px ->
+                            ComplicationLayoutParams.iteratePositions(
+                                { position: Int ->
+                                    setElementsTranslationXAtPosition(px, position)
+                                },
+                                POSITION_TOP or POSITION_BOTTOM
+                            )
+                        }
                     }
-                }
 
-                launch {
-                    dreamViewModel.dreamOverlayAlpha.collect { alpha ->
-                        ComplicationLayoutParams.iteratePositions(
-                            { position: Int ->
-                                setElementsAlphaAtPosition(
-                                    alpha = alpha,
-                                    position = position,
-                                    fadingOut = true,
-                                )
-                            },
-                            POSITION_TOP or POSITION_BOTTOM
-                        )
+                    launch {
+                        dreamViewModel.dreamOverlayAlpha.collect { alpha ->
+                            ComplicationLayoutParams.iteratePositions(
+                                { position: Int ->
+                                    setElementsAlphaAtPosition(
+                                        alpha = alpha,
+                                        position = position,
+                                        fadingOut = true,
+                                    )
+                                },
+                                POSITION_TOP or POSITION_BOTTOM
+                            )
+                        }
                     }
-                }
 
-                launch {
-                    dreamViewModel.transitionEnded.collect { _ ->
-                        mOverlayStateController.setExitAnimationsRunning(false)
+                    launch {
+                        dreamViewModel.transitionEnded.collect { _ ->
+                            mOverlayStateController.setExitAnimationsRunning(false)
+                        }
                     }
                 }
             }
-        }
+    }
+
+    fun destroy() {
+        mLifecycleFlowHandle?.dispose()
     }
 
     /**
@@ -243,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 76c7d23..3dd2561 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -59,6 +59,7 @@
 import com.android.systemui.util.ViewController;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.DisposableHandle;
 import kotlinx.coroutines.flow.FlowKt;
 
 import java.util.Arrays;
@@ -185,6 +186,8 @@
                 }
             };
 
+    private DisposableHandle mFlowHandle;
+
     @Inject
     public DreamOverlayContainerViewController(
             DreamOverlayContainerView containerView,
@@ -252,6 +255,17 @@
     }
 
     @Override
+    public void destroy() {
+        mStateController.removeCallback(mDreamOverlayStateCallback);
+        mStatusBarViewController.destroy();
+        mComplicationHostViewController.destroy();
+        mDreamOverlayAnimationsController.destroy();
+        mLowLightTransitionCoordinator.setLowLightEnterListener(null);
+
+        super.destroy();
+    }
+
+    @Override
     protected void onViewAttached() {
         mWakingUpFromSwipe = false;
         mJitterStartTimeMillis = System.currentTimeMillis();
@@ -263,7 +277,7 @@
         emptyRegion.recycle();
 
         if (dreamHandlesBeingObscured()) {
-            collectFlow(
+            mFlowHandle = collectFlow(
                     mView,
                     FlowKt.distinctUntilChanged(combineFlows(
                             mKeyguardTransitionInteractor.isFinishedIn(
@@ -295,6 +309,10 @@
 
     @Override
     protected void onViewDetached() {
+        if (mFlowHandle != null) {
+            mFlowHandle.dispose();
+            mFlowHandle = null;
+        }
         mHandler.removeCallbacksAndMessages(null);
         mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
         mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
@@ -363,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 7a9537b..ae2d401 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;
@@ -60,18 +59,22 @@
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.communal.shared.log.CommunalUiEvent;
 import com.android.systemui.communal.shared.model.CommunalScenes;
-import com.android.systemui.complication.Complication;
 import com.android.systemui.complication.dagger.ComplicationComponent;
 import com.android.systemui.dagger.qualifiers.Main;
 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;
 
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.concurrent.CancellationException;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -86,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;
@@ -129,16 +134,16 @@
      */
     private boolean mBouncerShowing = false;
 
-    private final ComplicationComponent mComplicationComponent;
+    private final com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
+            mDreamComplicationComponentFactory;
+    private final ComplicationComponent.Factory mComplicationComponentFactory;
+    private final DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
+    private final AmbientTouchComponent.Factory mAmbientTouchComponentFactory;
 
-    private final AmbientTouchComponent mAmbientTouchComponent;
+    private final TouchInsetManager mTouchInsetManager;
+    private final LifecycleOwner mLifecycleOwner;
 
-    private final com.android.systemui.dreams.complication.dagger.ComplicationComponent
-            mDreamComplicationComponent;
-
-    private final DreamOverlayComponent mDreamOverlayComponent;
-
-    private ComponentName mCurrentBlockedGestureDreamActivityComponent;
+    private final ArrayList<Job> mFlows = new ArrayList<>();
 
     /**
      * This {@link LifecycleRegistry} controls when dream overlay functionality, like touch
@@ -180,6 +185,7 @@
                         mShadeExpanded = expanded;
 
                         updateLifecycleStateLocked();
+                        updateGestureBlockingLocked();
                     });
                 }
             };
@@ -210,20 +216,127 @@
                 mBouncerShowing = bouncerShowing;
 
                 updateLifecycleStateLocked();
+                updateGestureBlockingLocked();
             });
         }
     };
 
-    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;
 
@@ -285,36 +398,27 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
         mStateController = stateController;
         mUiEventLogger = uiEventLogger;
+        mComplicationComponentFactory = complicationComponentFactory;
+        mDreamComplicationComponentFactory = dreamComplicationComponentFactory;
         mDreamOverlayCallbackController = dreamOverlayCallbackController;
         mWindowTitle = windowTitle;
         mCommunalInteractor = communalInteractor;
         mSystemDialogsCloser = systemDialogsCloser;
         mGestureInteractor = gestureInteractor;
-
-        final ViewModelStore viewModelStore = new ViewModelStore();
-        final Complication.Host host =
-                () -> mExecutor.execute(DreamOverlayService.this::requestExit);
-
-        mComplicationComponent = complicationComponentFactory.create(lifecycleOwner, host,
-                viewModelStore, touchInsetManager);
-        mDreamComplicationComponent = dreamComplicationComponentFactory.create(
-                mComplicationComponent.getVisibilityController(), touchInsetManager);
-        mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
-                mComplicationComponent.getComplicationHostViewController(), touchInsetManager);
-        mAmbientTouchComponent = ambientTouchComponentFactory.create(lifecycleOwner,
-                new HashSet<>(Arrays.asList(
-                        mDreamComplicationComponent.getHideComplicationTouchHandler(),
-                        mDreamOverlayComponent.getCommunalTouchHandler())));
+        mDreamOverlayComponentFactory = dreamOverlayComponentFactory;
+        mAmbientTouchComponentFactory = ambientTouchComponentFactory;
+        mTouchInsetManager = touchInsetManager;
+        mLifecycleOwner = lifecycleOwner;
         mLifecycleRegistry = lifecycleOwner.getRegistry();
 
         mExecutor.execute(() -> setLifecycleStateLocked(Lifecycle.State.CREATED));
 
-        collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
-                mIsCommunalAvailableCallback);
-        collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
-                mCommunalVisibleConsumer);
-        collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
-                mBouncerShowingConsumer);
+        mFlows.add(collectFlow(getLifecycle(), mCommunalInteractor.isCommunalAvailable(),
+                mIsCommunalAvailableCallback));
+        mFlows.add(collectFlow(getLifecycle(), communalInteractor.isCommunalVisible(),
+                mCommunalVisibleConsumer));
+        mFlows.add(collectFlow(getLifecycle(), keyguardInteractor.primaryBouncerShowing,
+                mBouncerShowingConsumer));
     }
 
     @NonNull
@@ -339,12 +443,15 @@
     public void onDestroy() {
         mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
 
+        for (Job job : mFlows) {
+            job.cancel(new CancellationException());
+        }
+        mFlows.clear();
+
         mExecutor.execute(() -> {
             setLifecycleStateLocked(Lifecycle.State.DESTROYED);
-
-            resetCurrentDreamOverlayLocked();
-
             mDestroyed = true;
+            mResetHandler.reset("destroying");
         });
 
         mDispatcher.onServicePreSuperOnDestroy();
@@ -353,6 +460,23 @@
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+        final ComplicationComponent complicationComponent = mComplicationComponentFactory.create(
+                mLifecycleOwner,
+                () -> mExecutor.execute(DreamOverlayService.this::requestExit),
+                new ViewModelStore(), mTouchInsetManager);
+        final com.android.systemui.dreams.complication.dagger.ComplicationComponent
+                dreamComplicationComponent = mDreamComplicationComponentFactory.create(
+                complicationComponent.getVisibilityController(), mTouchInsetManager);
+
+        final DreamOverlayComponent dreamOverlayComponent = mDreamOverlayComponentFactory.create(
+                mLifecycleOwner, complicationComponent.getComplicationHostViewController(),
+                mTouchInsetManager);
+        final AmbientTouchComponent ambientTouchComponent = mAmbientTouchComponentFactory.create(
+                mLifecycleOwner,
+                new HashSet<>(Arrays.asList(
+                        dreamComplicationComponent.getHideComplicationTouchHandler(),
+                        dreamOverlayComponent.getCommunalTouchHandler())), TAG);
+
         setLifecycleStateLocked(Lifecycle.State.STARTED);
 
         mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
@@ -367,19 +491,22 @@
             // 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 =
-                mDreamOverlayComponent.getDreamOverlayContainerViewController();
-        mTouchMonitor = mAmbientTouchComponent.getTouchMonitor();
+                dreamOverlayComponent.getDreamOverlayContainerViewController();
+        mTouchMonitor = ambientTouchComponent.getTouchMonitor();
         mTouchMonitor.init();
 
         mStateController.setShouldShowComplications(shouldShowComplications());
 
         // 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;
         }
 
@@ -400,7 +527,7 @@
         mStarted = true;
 
         updateRedirectWakeup();
-        updateBlockedGestureDreamActivityComponent();
+        updateGestureBlockingLocked();
     }
 
     private void updateRedirectWakeup() {
@@ -411,21 +538,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
@@ -436,6 +551,18 @@
                 null);
     }
 
+    private void updateGestureBlockingLocked() {
+        final boolean shouldBlock = !isDreamInPreviewMode() && !mShadeExpanded && !mBouncerShowing;
+
+        if (shouldBlock) {
+            mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER,
+                    GestureInteractor.Scope.Global);
+        } else {
+            mGestureInteractor.removeGestureBlockedMatcher(DREAM_TYPE_MATCHER,
+                    GestureInteractor.Scope.Global);
+        }
+    }
+
     private Lifecycle.State getLifecycleStateLocked() {
         return mLifecycleRegistry.getCurrentState();
     }
@@ -461,7 +588,7 @@
     public void onWakeUp() {
         if (mDreamOverlayContainerViewController != null) {
             mDreamOverlayCallbackController.onWakeUp();
-            mDreamOverlayContainerViewController.wakeUp();
+            mDreamOverlayContainerViewController.onWakeUp();
         }
     }
 
@@ -541,6 +668,10 @@
     }
 
     private void removeContainerViewFromParentLocked() {
+        if (mDreamOverlayContainerViewController == null) {
+            return;
+        }
+
         View containerView = mDreamOverlayContainerViewController.getContainerView();
         if (containerView == null) {
             return;
@@ -552,41 +683,4 @@
         Log.w(TAG, "Removing dream overlay container view parent!");
         parentView.removeView(containerView);
     }
-
-    private void resetCurrentDreamOverlayLocked() {
-        if (mStateController.areExitAnimationsRunning()) {
-            mStateController.addCallback(mExitAnimationFinishedCallback);
-            return;
-        }
-
-        if (mStarted && mWindow != null) {
-            try {
-                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);
-
-        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/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index ee7b6f5..5ba780f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -33,7 +33,11 @@
 import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
+import kotlinx.coroutines.Job;
+
+import java.util.ArrayList;
 import java.util.Optional;
+import java.util.concurrent.CancellationException;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -49,6 +53,8 @@
     private final ConfigurationInteractor mConfigurationInteractor;
     private Boolean mIsEnabled = false;
 
+    private ArrayList<Job> mFlows = new ArrayList<>();
+
     private int mLayoutDirection = LayoutDirection.LTR;
 
     @VisibleForTesting
@@ -70,17 +76,17 @@
         mCommunalInteractor = communalInteractor;
         mConfigurationInteractor = configurationInteractor;
 
-        collectFlow(
+        mFlows.add(collectFlow(
                 mLifecycle,
                 mCommunalInteractor.isCommunalAvailable(),
                 mIsCommunalAvailableCallback
-        );
+        ));
 
-        collectFlow(
+        mFlows.add(collectFlow(
                 mLifecycle,
                 mConfigurationInteractor.getLayoutDirection(),
                 mLayoutDirectionCallback
-        );
+        ));
     }
 
     @Override
@@ -140,4 +146,13 @@
             }
         });
     }
+
+    @Override
+    public void onDestroy() {
+        for (Job job : mFlows) {
+            job.cancel(new CancellationException());
+        }
+        mFlows.clear();
+        TouchHandler.super.onDestroy();
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt
similarity index 67%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
copy to packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt
index 3c5beeb..8682848 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/EduDeviceConnectionTime.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * 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.
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.systemui.education.data.model
 
-parcelable BubbleBarLocation;
\ No newline at end of file
+import java.time.Instant
+
+data class EduDeviceConnectionTime(
+    val keyboardFirstConnectionTime: Instant? = null,
+    val touchpadFirstConnectionTime: Instant? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 4fd79d7..01f838f 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
 import com.android.systemui.education.data.model.GestureEduModel
 import java.time.Instant
 import javax.inject.Inject
@@ -53,10 +54,16 @@
 
     fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
 
+    fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime>
+
     suspend fun updateGestureEduModel(
         gestureType: GestureType,
         transform: (GestureEduModel) -> GestureEduModel
     )
+
+    suspend fun updateEduDeviceConnectionTime(
+        transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
+    )
 }
 
 /**
@@ -76,6 +83,8 @@
         const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
         const val USAGE_SESSION_START_TIME_SUFFIX = "_USAGE_SESSION_START_TIME"
         const val LAST_EDUCATION_TIME_SUFFIX = "_LAST_EDUCATION_TIME"
+        const val KEYBOARD_FIRST_CONNECTION_TIME = "KEYBOARD_FIRST_CONNECTION_TIME"
+        const val TOUCHPAD_FIRST_CONNECTION_TIME = "TOUCHPAD_FIRST_CONNECTION_TIME"
 
         const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
     }
@@ -158,6 +167,37 @@
         }
     }
 
+    override fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime> =
+        prefData.map { preferences -> getEduDeviceConnectionTime(preferences) }
+
+    override suspend fun updateEduDeviceConnectionTime(
+        transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
+    ) {
+        datastore.filterNotNull().first().edit { preferences ->
+            val currentModel = getEduDeviceConnectionTime(preferences)
+            val updatedModel = transform(currentModel)
+            setInstant(
+                preferences,
+                updatedModel.keyboardFirstConnectionTime,
+                getKeyboardFirstConnectionTimeKey()
+            )
+            setInstant(
+                preferences,
+                updatedModel.touchpadFirstConnectionTime,
+                getTouchpadFirstConnectionTimeKey()
+            )
+        }
+    }
+
+    private fun getEduDeviceConnectionTime(preferences: Preferences): EduDeviceConnectionTime {
+        return EduDeviceConnectionTime(
+            keyboardFirstConnectionTime =
+                preferences[getKeyboardFirstConnectionTimeKey()]?.let { Instant.ofEpochSecond(it) },
+            touchpadFirstConnectionTime =
+                preferences[getTouchpadFirstConnectionTimeKey()]?.let { Instant.ofEpochSecond(it) }
+        )
+    }
+
     private fun getSignalCountKey(gestureType: GestureType): Preferences.Key<Int> =
         intPreferencesKey(gestureType.name + SIGNAL_COUNT_SUFFIX)
 
@@ -173,6 +213,12 @@
     private fun getLastEducationTimeKey(gestureType: GestureType): Preferences.Key<Long> =
         longPreferencesKey(gestureType.name + LAST_EDUCATION_TIME_SUFFIX)
 
+    private fun getKeyboardFirstConnectionTimeKey(): Preferences.Key<Long> =
+        longPreferencesKey(KEYBOARD_FIRST_CONNECTION_TIME)
+
+    private fun getTouchpadFirstConnectionTimeKey(): Preferences.Key<Long> =
+        longPreferencesKey(TOUCHPAD_FIRST_CONNECTION_TIME)
+
     private fun setInstant(
         preferences: MutablePreferences,
         instant: Instant?,
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
index db5c386..10be26e 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/ContextualEducationInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
 import com.android.systemui.education.data.model.GestureEduModel
 import com.android.systemui.education.data.repository.ContextualEducationRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -32,6 +33,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.launch
 
@@ -67,6 +69,10 @@
             .flowOn(backgroundDispatcher)
     }
 
+    suspend fun getEduDeviceConnectionTime(): EduDeviceConnectionTime {
+        return repository.readEduDeviceConnectionTime().first()
+    }
+
     suspend fun incrementSignalCount(gestureType: GestureType) {
         repository.updateGestureEduModel(gestureType) {
             it.copy(
@@ -100,4 +106,16 @@
             it.copy(usageSessionStartTime = clock.instant(), signalCount = 1)
         }
     }
+
+    suspend fun updateKeyboardFirstConnectionTime() {
+        repository.updateEduDeviceConnectionTime {
+            it.copy(keyboardFirstConnectionTime = clock.instant())
+        }
+    }
+
+    suspend fun updateTouchpadFirstConnectionTime() {
+        repository.updateEduDeviceConnectionTime {
+            it.copy(touchpadFirstConnectionTime = clock.instant())
+        }
+    }
 }
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 ad3335b..3105527 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,7 +16,13 @@
 
 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.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -24,10 +30,15 @@
 import com.android.systemui.education.data.model.GestureEduModel
 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
@@ -39,10 +50,13 @@
 constructor(
     @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
     }
@@ -50,6 +64,24 @@
     private val _educationTriggered = MutableStateFlow<EducationInfo?>(null)
     val educationTriggered = _educationTriggered.asStateFlow()
 
+    private val keyboardShortcutTriggered: Flow<GestureType> = conflatedCallbackFlow {
+        val listener = KeyGestureEventListener { event ->
+            // Only store keyboard shortcut time for gestures providing keyboard education
+            val shortcutType =
+                when (event.keyGestureType) {
+                    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 {
@@ -61,6 +93,38 @@
                 }
             }
         }
+
+        backgroundScope.launch {
+            userInputDeviceRepository.isAnyTouchpadConnectedForUser.collect {
+                if (
+                    it.isConnected &&
+                        contextualEducationInteractor
+                            .getEduDeviceConnectionTime()
+                            .touchpadFirstConnectionTime == null
+                ) {
+                    contextualEducationInteractor.updateTouchpadFirstConnectionTime()
+                }
+            }
+        }
+
+        backgroundScope.launch {
+            userInputDeviceRepository.isAnyKeyboardConnectedForUser.collect {
+                if (
+                    it.isConnected &&
+                        contextualEducationInteractor
+                            .getEduDeviceConnectionTime()
+                            .keyboardFirstConnectionTime == null
+                ) {
+                    contextualEducationInteractor.updateKeyboardFirstConnectionTime()
+                }
+            }
+        }
+
+        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 e5f3a57..95cd9eb 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")
@@ -111,9 +98,6 @@
     @JvmField
     val AUTO_PIN_CONFIRMATION = releasedFlag("auto_pin_confirmation", "auto_pin_confirmation")
 
-    // TODO(b/262859270): Tracking Bug
-    @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag("falsing_off_for_unfolded")
-
     /** Enables code to show contextual loyalty cards in wallet entrypoints */
     // TODO(b/294110497): Tracking Bug
     @JvmField
@@ -125,13 +109,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 +127,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 +171,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,26 +190,18 @@
     @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")
-
-    // TODO(b/293585143): Tracking Bug
-    val INSTANT_TETHER = releasedFlag("instant_tether")
-
-    // TODO(b/294588085): Tracking Bug
-    val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
+    @JvmField
+    val INCOMPATIBLE_CHARGING_BATTERY_ICON = releasedFlag("incompatible_charging_battery_icon")
 
     // 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")
@@ -254,9 +224,6 @@
     // TODO(b/254512697): Tracking Bug
     val MEDIA_TAP_TO_TRANSFER = releasedFlag("media_tap_to_transfer")
 
-    // TODO(b/254512502): Tracking Bug
-    val MEDIA_SESSION_ACTIONS = unreleasedFlag("media_session_actions")
-
     // TODO(b/254512654): Tracking Bug
     @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag("dream_media_complication")
 
@@ -280,8 +247,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 +270,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 +291,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
 
@@ -337,10 +300,6 @@
     // TODO(b/278714186) Tracking Bug
     @JvmField
     val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag("clipboard_image_timeout", teamfood = true)
-    // TODO(b/279405451): Tracking Bug
-    @JvmField
-    val CLIPBOARD_SHARED_TRANSITIONS =
-            unreleasedFlag("clipboard_shared_transitions", teamfood = true)
 
     // 1900
     @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
@@ -368,14 +327,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
@@ -419,28 +376,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/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index 567bf70..ca43871 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -35,6 +35,7 @@
 import android.util.Log
 import android.util.Size
 import androidx.core.content.res.ResourcesCompat
+import com.android.app.tracing.traceSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -162,20 +163,21 @@
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
-    ): Bitmap? {
-        return try {
-            ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
-                configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
-                decoder.allocator = allocator
+    ): Bitmap? =
+        traceSection("ImageLoader#loadBitmap") {
+            return try {
+                ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+                    configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+                    decoder.allocator = allocator
+                }
+            } catch (e: IOException) {
+                Log.w(TAG, "Failed to load source $source", e)
+                return null
+            } catch (e: DecodeException) {
+                Log.w(TAG, "Failed to decode source $source", e)
+                return null
             }
-        } catch (e: IOException) {
-            Log.w(TAG, "Failed to load source $source", e)
-            return null
-        } catch (e: DecodeException) {
-            Log.w(TAG, "Failed to decode source $source", e)
-            return null
         }
-    }
 
     /**
      * Loads passed [Source] on a background thread and returns the [Drawable].
@@ -253,28 +255,31 @@
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
-    ): Drawable? {
-        return try {
-            loadDrawableSync(
-                toImageDecoderSource(source, defaultContext),
-                maxWidth,
-                maxHeight,
-                allocator
-            )
-                ?:
-                // If we have a resource, retry fallback using the "normal" Resource loading system.
-                // This will come into effect in cases like trying to load AnimatedVectorDrawable.
-                if (source is Res) {
-                    val context = source.context ?: defaultContext
-                    ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
-                } else {
-                    null
-                }
-        } catch (e: NotFoundException) {
-            Log.w(TAG, "Couldn't load resource $source", e)
-            null
+    ): Drawable? =
+        traceSection("ImageLoader#loadDrawable") {
+            return try {
+                loadDrawableSync(
+                    toImageDecoderSource(source, defaultContext),
+                    maxWidth,
+                    maxHeight,
+                    allocator
+                )
+                    ?:
+                    // If we have a resource, retry fallback using the "normal" Resource loading
+                    // system.
+                    // This will come into effect in cases like trying to load
+                    // AnimatedVectorDrawable.
+                    if (source is Res) {
+                        val context = source.context ?: defaultContext
+                        ResourcesCompat.getDrawable(context.resources, source.resId, context.theme)
+                    } else {
+                        null
+                    }
+            } catch (e: NotFoundException) {
+                Log.w(TAG, "Couldn't load resource $source", e)
+                null
+            }
         }
-    }
 
     /**
      * Loads passed [ImageDecoder.Source] synchronously and returns the drawable.
@@ -297,20 +302,21 @@
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
-    ): Drawable? {
-        return try {
-            ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
-                configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
-                decoder.allocator = allocator
+    ): Drawable? =
+        traceSection("ImageLoader#loadDrawable") {
+            return try {
+                ImageDecoder.decodeDrawable(source) { decoder, info, _ ->
+                    configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
+                    decoder.allocator = allocator
+                }
+            } catch (e: IOException) {
+                Log.w(TAG, "Failed to load source $source", e)
+                return null
+            } catch (e: DecodeException) {
+                Log.w(TAG, "Failed to decode source $source", e)
+                return null
             }
-        } catch (e: IOException) {
-            Log.w(TAG, "Failed to load source $source", e)
-            return null
-        } catch (e: DecodeException) {
-            Log.w(TAG, "Failed to decode source $source", e)
-            return null
         }
-    }
 
     /** Loads icon drawable while attempting to size restrict the drawable. */
     @WorkerThread
@@ -320,55 +326,59 @@
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
-    ): Drawable? {
-        return when (icon.type) {
-            Icon.TYPE_URI,
-            Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
-                val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
-                loadDrawableSync(source, maxWidth, maxHeight, allocator)
-            }
-            Icon.TYPE_RESOURCE -> {
-                val resources = resolveResourcesForIcon(context, icon)
-                resources?.let {
+    ): Drawable? =
+        traceSection("ImageLoader#loadDrawable") {
+            return when (icon.type) {
+                Icon.TYPE_URI,
+                Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+                    val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+                    loadDrawableSync(source, maxWidth, maxHeight, allocator)
+                }
+                Icon.TYPE_RESOURCE -> {
+                    val resources = resolveResourcesForIcon(context, icon)
+                    resources?.let {
+                        loadDrawableSync(
+                            ImageDecoder.createSource(it, icon.resId),
+                            maxWidth,
+                            maxHeight,
+                            allocator
+                        )
+                    }
+                        // Fallback to non-ImageDecoder load if the attempt failed (e.g. the
+                        // resource
+                        // is a Vector drawable which ImageDecoder doesn't support.)
+                        ?: loadIconDrawable(icon, context)
+                }
+                Icon.TYPE_BITMAP -> {
+                    BitmapDrawable(context.resources, icon.bitmap)
+                }
+                Icon.TYPE_ADAPTIVE_BITMAP -> {
+                    AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
+                }
+                Icon.TYPE_DATA -> {
                     loadDrawableSync(
-                        ImageDecoder.createSource(it, icon.resId),
+                        ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
                         maxWidth,
                         maxHeight,
                         allocator
                     )
                 }
-                // Fallback to non-ImageDecoder load if the attempt failed (e.g. the resource
-                // is a Vector drawable which ImageDecoder doesn't support.)
-                ?: loadIconDrawable(icon, context)
+                else -> {
+                    // We don't recognize this icon, just fallback.
+                    loadIconDrawable(icon, context)
+                }
+            }?.let { drawable ->
+                // Icons carry tint which we need to propagate down to a Drawable.
+                tintDrawable(icon, drawable)
+                drawable
             }
-            Icon.TYPE_BITMAP -> {
-                BitmapDrawable(context.resources, icon.bitmap)
-            }
-            Icon.TYPE_ADAPTIVE_BITMAP -> {
-                AdaptiveIconDrawable(null, BitmapDrawable(context.resources, icon.bitmap))
-            }
-            Icon.TYPE_DATA -> {
-                loadDrawableSync(
-                    ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
-                    maxWidth,
-                    maxHeight,
-                    allocator
-                )
-            }
-            else -> {
-                // We don't recognize this icon, just fallback.
-                loadIconDrawable(icon, context)
-            }
-        }?.let { drawable ->
-            // Icons carry tint which we need to propagate down to a Drawable.
-            tintDrawable(icon, drawable)
-            drawable
         }
-    }
 
     @WorkerThread
     fun loadIconDrawable(icon: Icon, context: Context): Drawable? {
-        icon.loadDrawable(context)?.let { return it }
+        icon.loadDrawable(context)?.let {
+            return it
+        }
 
         Log.w(TAG, "Failed to load drawable for $icon")
         return null
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/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
index e8e1dd4..7ecacdc 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt
@@ -18,20 +18,20 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
+import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
 import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial
 import dagger.Lazy
 import javax.inject.Inject
 
-/** A [CoreStartable] to launch a scheduler for keyboard and touchpad education */
+/** A [CoreStartable] to launch a scheduler for keyboard and touchpad tutorial notification */
 @SysUISingleton
 class KeyboardTouchpadTutorialCoreStartable
 @Inject
-constructor(private val tutorialSchedulerInteractor: Lazy<TutorialSchedulerInteractor>) :
+constructor(private val tutorialNotificationCoordinator: Lazy<TutorialNotificationCoordinator>) :
     CoreStartable {
     override fun start() {
         if (newTouchpadGesturesTutorial()) {
-            tutorialSchedulerInteractor.get().start()
+            tutorialNotificationCoordinator.get().start()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
index a8d7dad..cfc913f 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractor.kt
@@ -17,9 +17,7 @@
 package com.android.systemui.inputdevice.tutorial.domain.interactor
 
 import android.os.SystemProperties
-import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType
 import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
 import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
@@ -31,23 +29,22 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.hours
 import kotlin.time.toKotlinDuration
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.launch
 
 /**
- * When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], then launch the
- * tutorial as soon as there's a connected device
+ * When the first time a keyboard or touchpad is connected, wait for [LAUNCH_DELAY], and as soon as
+ * there's a connected device, show a notification to launch the tutorial.
  */
 @SysUISingleton
 class TutorialSchedulerInteractor
 @Inject
 constructor(
-    @Background private val backgroundScope: CoroutineScope,
     keyboardRepository: KeyboardRepository,
     touchpadRepository: TouchpadRepository,
     private val repo: TutorialSchedulerRepository
@@ -58,17 +55,6 @@
             TOUCHPAD to touchpadRepository.isAnyTouchpadConnected
         )
 
-    fun start() {
-        backgroundScope.launch {
-            // Merging two flows to ensure that launch tutorial is launched consecutively in order
-            // to avoid race condition
-            merge(touchpadScheduleFlow, keyboardScheduleFlow).collect {
-                val tutorialType = resolveTutorialType(it)
-                launchTutorial(tutorialType)
-            }
-        }
-    }
-
     private val touchpadScheduleFlow = flow {
         if (!repo.isLaunched(TOUCHPAD)) {
             schedule(TOUCHPAD)
@@ -95,14 +81,19 @@
     private suspend fun waitForDeviceConnection(deviceType: DeviceType) =
         isAnyDeviceConnected[deviceType]!!.filter { it }.first()
 
-    private suspend fun launchTutorial(tutorialType: TutorialType) {
-        if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH)
-            repo.updateLaunchTime(KEYBOARD, Instant.now())
-        if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH)
-            repo.updateLaunchTime(TOUCHPAD, Instant.now())
-        // TODO: launch tutorial
-        Log.d(TAG, "Launch tutorial for $tutorialType")
-    }
+    // Merging two flows ensures that tutorial is launched consecutively to avoid race condition
+    val tutorials: Flow<TutorialType> =
+        merge(touchpadScheduleFlow, keyboardScheduleFlow).map {
+            val tutorialType = resolveTutorialType(it)
+
+            // TODO: notifying time is not oobe launching time - move these updates into oobe
+            if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH)
+                repo.updateLaunchTime(KEYBOARD, Instant.now())
+            if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH)
+                repo.updateLaunchTime(TOUCHPAD, Instant.now())
+
+            tutorialType
+        }
 
     private suspend fun resolveTutorialType(deviceType: DeviceType): TutorialType {
         // Resolve the type of tutorial depending on which device are connected when the tutorial is
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
new file mode 100644
index 0000000..5d9dda3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
@@ -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.systemui.inputdevice.tutorial.ui
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.core.app.NotificationCompat
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.Companion.TAG
+import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.TutorialType
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_BOTH
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_TOUCHPAD
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** When the scheduler is due, show a notification to launch tutorial */
+@SysUISingleton
+class TutorialNotificationCoordinator
+@Inject
+constructor(
+    @Background private val backgroundScope: CoroutineScope,
+    @Application private val context: Context,
+    private val tutorialSchedulerInteractor: TutorialSchedulerInteractor,
+    private val notificationManager: NotificationManager
+) {
+    fun start() {
+        backgroundScope.launch {
+            tutorialSchedulerInteractor.tutorials.collect { showNotification(it) }
+        }
+    }
+
+    // By sharing the same tag and id, we update the content of existing notification instead of
+    // creating multiple notifications
+    private fun showNotification(tutorialType: TutorialType) {
+        if (tutorialType == TutorialType.NONE) return
+
+        if (notificationManager.getNotificationChannel(CHANNEL_ID) == null)
+            createNotificationChannel()
+
+        // Replace "System UI" app name with "Android System"
+        val extras = Bundle()
+        extras.putString(
+            Notification.EXTRA_SUBSTITUTE_APP_NAME,
+            context.getString(com.android.internal.R.string.android_system_label)
+        )
+
+        val info = getNotificationInfo(tutorialType)!!
+        val notification =
+            NotificationCompat.Builder(context, CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_settings)
+                .setContentTitle(info.title)
+                .setContentText(info.text)
+                .setContentIntent(createPendingIntent(info.type))
+                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+                .setAutoCancel(true)
+                .addExtras(extras)
+                .build()
+
+        notificationManager.notify(TAG, NOTIFICATION_ID, notification)
+    }
+
+    private fun createNotificationChannel() {
+        val channel =
+            NotificationChannel(
+                CHANNEL_ID,
+                context.getString(com.android.internal.R.string.android_system_label),
+                NotificationManager.IMPORTANCE_DEFAULT
+            )
+        notificationManager.createNotificationChannel(channel)
+    }
+
+    private fun createPendingIntent(tutorialType: String): PendingIntent {
+        val intent =
+            Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
+                putExtra(INTENT_TUTORIAL_TYPE_KEY, tutorialType)
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            }
+        return PendingIntent.getActivity(
+            context,
+            /* requestCode= */ 0,
+            intent,
+            PendingIntent.FLAG_IMMUTABLE
+        )
+    }
+
+    private data class NotificationInfo(val title: String, val text: String, val type: String)
+
+    private fun getNotificationInfo(tutorialType: TutorialType): NotificationInfo? =
+        when (tutorialType) {
+            TutorialType.KEYBOARD ->
+                NotificationInfo(
+                    context.getString(R.string.launch_keyboard_tutorial_notification_title),
+                    context.getString(R.string.launch_keyboard_tutorial_notification_content),
+                    INTENT_TUTORIAL_TYPE_KEYBOARD
+                )
+            TutorialType.TOUCHPAD ->
+                NotificationInfo(
+                    context.getString(R.string.launch_touchpad_tutorial_notification_title),
+                    context.getString(R.string.launch_touchpad_tutorial_notification_content),
+                    INTENT_TUTORIAL_TYPE_TOUCHPAD
+                )
+            TutorialType.BOTH ->
+                NotificationInfo(
+                    context.getString(
+                        R.string.launch_keyboard_touchpad_tutorial_notification_title
+                    ),
+                    context.getString(
+                        R.string.launch_keyboard_touchpad_tutorial_notification_content
+                    ),
+                    INTENT_TUTORIAL_TYPE_BOTH
+                )
+            TutorialType.NONE -> null
+        }
+
+    companion object {
+        private const val CHANNEL_ID = "TutorialSchedulerNotificationChannel"
+        private const val NOTIFICATION_ID = 5566
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
index 6bc640d..1aa5ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
@@ -20,7 +20,6 @@
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -97,7 +96,6 @@
     val secondaryFixedDim = LocalAndroidColorScheme.current.secondaryFixedDim
     val onSecondaryFixed = LocalAndroidColorScheme.current.onSecondaryFixed
     val onSecondaryFixedVariant = LocalAndroidColorScheme.current.onSecondaryFixedVariant
-    val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
     val dynamicProperties =
         rememberLottieDynamicProperties(
             rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
@@ -106,11 +104,10 @@
             rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant)
         )
     val screenColors =
-        remember(surfaceContainer, dynamicProperties) {
+        remember(dynamicProperties) {
             TutorialScreenConfig.Colors(
                 background = onSecondaryFixed,
-                successBackground = surfaceContainer,
-                title = primaryFixedDim,
+                title = secondaryFixedDim,
                 animationColors = dynamicProperties,
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index c50b7dc..b271356 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -24,13 +24,13 @@
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.EnterTransition
 import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -46,7 +46,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.res.stringResource
@@ -60,6 +59,7 @@
 import com.airbnb.lottie.compose.animateLottieCompositionAsState
 import com.airbnb.lottie.compose.rememberLottieComposition
 import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.compose.modifiers.background
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.FINISHED
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.IN_PROGRESS
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NOT_STARTED
@@ -76,19 +76,11 @@
     onDoneButtonClicked: () -> Unit,
     config: TutorialScreenConfig
 ) {
-    val animatedColor by
-        animateColorAsState(
-            targetValue =
-                if (actionState == FINISHED) config.colors.successBackground
-                else config.colors.background,
-            animationSpec = tween(durationMillis = 150, easing = LinearEasing),
-            label = "backgroundColor"
-        )
     Column(
         verticalArrangement = Arrangement.Center,
         modifier =
             Modifier.fillMaxSize()
-                .drawBehind { drawRect(animatedColor) }
+                .background(config.colors.background)
                 .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
     ) {
         Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
index 0406bb9..55e5f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
@@ -29,7 +29,6 @@
 
     data class Colors(
         val background: Color,
-        val successBackground: Color,
         val title: Color,
         val animationColors: LottieDynamicProperties
     )
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index 8debe79..1adc285 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -54,6 +54,7 @@
         const val INTENT_TUTORIAL_TYPE_KEY = "tutorial_type"
         const val INTENT_TUTORIAL_TYPE_TOUCHPAD = "touchpad"
         const val INTENT_TUTORIAL_TYPE_KEYBOARD = "keyboard"
+        const val INTENT_TUTORIAL_TYPE_BOTH = "both"
     }
 
     private val vm by
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..1bc91ca 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;
@@ -78,7 +76,9 @@
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
 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;
@@ -123,6 +123,7 @@
     private final PowerInteractor mPowerInteractor;
     private final KeyguardInteractor mKeyguardInteractor;
     private final Lazy<SceneInteractor> mSceneInteractorLazy;
+    private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractorLazy;
     private final Executor mMainExecutor;
     private final Lazy<KeyguardStateCallbackStartable> mKeyguardStateCallbackStartableLazy;
 
@@ -319,6 +320,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 +348,9 @@
             KeyguardInteractor keyguardInteractor,
             KeyguardEnabledInteractor keyguardEnabledInteractor,
             Lazy<KeyguardStateCallbackStartable> keyguardStateCallbackStartableLazy,
-            KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor) {
+            KeyguardWakeDirectlyToGoneInteractor keyguardWakeDirectlyToGoneInteractor,
+            KeyguardDismissInteractor keyguardDismissInteractor,
+            Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -359,6 +363,7 @@
         mSceneInteractorLazy = sceneInteractorLazy;
         mMainExecutor = mainExecutor;
         mKeyguardStateCallbackStartableLazy = keyguardStateCallbackStartableLazy;
+        mDeviceEntryInteractorLazy = deviceEntryInteractorLazy;
 
         if (KeyguardWmStateRefactor.isEnabled()) {
             WindowManagerLockscreenVisibilityViewBinder.bind(
@@ -375,6 +380,7 @@
         mWmOcclusionManager = windowManagerOcclusionManager;
         mKeyguardEnabledInteractor = keyguardEnabledInteractor;
         mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor;
+        mKeyguardDismissInteractor = keyguardDismissInteractor;
     }
 
     @Override
@@ -482,7 +488,13 @@
         public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
             trace("dismiss message=" + message);
             checkPermission();
-            mKeyguardViewMediator.dismiss(callback, message);
+            if (SceneContainerFlag.isEnabled()) {
+                mDeviceEntryInteractorLazy.get().attemptDeviceEntry(callback);
+            } else if (KeyguardWmStateRefactor.isEnabled()) {
+                mKeyguardDismissInteractor.dismissKeyguardWithCallback(callback);
+            } else {
+                mKeyguardViewMediator.dismiss(callback, message);
+            }
         }
 
         @Override // Binder interface
@@ -672,9 +684,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..d38c952 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);
                     }
@@ -3588,12 +3568,16 @@
                     }
                     return;
                 }
-                try {
-                    mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
-                            mContext.getPackageName(),
-                            mSelectedUserInteractor.getSelectedUserId(true));
-                } catch (RemoteException e) {
-                    Log.d(TAG, "Failed to set disable flags: " + flags, e);
+
+                // Handled in StatusBarDisableFlagsInteractor.
+                if (!KeyguardWmStateRefactor.isEnabled()) {
+                    try {
+                        mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+                                mContext.getPackageName(),
+                                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/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index b1589da..e68d799 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -39,7 +39,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
 import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.buffer
@@ -53,7 +53,7 @@
 /** Encapsulates state about device entry fingerprint auth mechanism. */
 interface DeviceEntryFingerprintAuthRepository {
     /** Whether the device entry fingerprint auth is locked out. */
-    val isLockedOut: Flow<Boolean>
+    val isLockedOut: StateFlow<Boolean>
 
     /**
      * Whether the fingerprint sensor is currently listening, this doesn't mean that the user is
@@ -127,7 +127,7 @@
         else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
     }
 
-    override val isLockedOut: Flow<Boolean> =
+    override val isLockedOut: StateFlow<Boolean> by lazy {
         conflatedCallbackFlow {
                 val sendLockoutUpdate =
                     fun() {
@@ -151,7 +151,12 @@
                 sendLockoutUpdate()
                 awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
             }
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+            .stateIn(
+                scope,
+                started = Eagerly,
+                initialValue = keyguardUpdateMonitor.isFingerprintLockedOut
+            )
+    }
 
     override val isRunning: Flow<Boolean>
         get() =
@@ -309,6 +314,7 @@
                         ) {
                             sendShouldUpdateIndicatorVisibility(true)
                         }
+
                         override fun onStrongAuthStateChanged(userId: Int) {
                             sendShouldUpdateIndicatorVisibility(true)
                         }
@@ -318,7 +324,7 @@
                 awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
             }
             .flowOn(mainDispatcher)
-            .shareIn(scope, started = SharingStarted.WhileSubscribed(), replay = 1)
+            .shareIn(scope, started = WhileSubscribed(), replay = 1)
 
     companion object {
         const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 7087752..ec52055 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -101,7 +101,7 @@
         secureSettings
             .observerFlow(
                 names = arrayOf(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
-                userId = UserHandle.USER_SYSTEM,
+                userId = UserHandle.USER_ALL,
             )
             .onStart { emit(Unit) } // Forces an initial update.
             .map { withContext(backgroundDispatcher) { getClockSize() } }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
index a1e4af5..b67fd4b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.content.Context
+import android.os.UserHandle
 import android.provider.Settings
 import android.view.View
 import com.android.systemui.dagger.SysUISingleton
@@ -37,6 +38,7 @@
 interface KeyguardSmartspaceRepository {
     val bcSmartspaceVisibility: StateFlow<Int>
     val isWeatherEnabled: StateFlow<Boolean>
+
     fun setBcSmartspaceVisibility(visibility: Int)
 }
 
@@ -55,7 +57,7 @@
         secureSettings
             .observerFlow(
                 names = arrayOf(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED),
-                userId = userTracker.userId,
+                userId = UserHandle.USER_ALL,
             )
             .onStart { emit(Unit) }
             .map { getLockscreenWeatherEnabled() }
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/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 49e4c70..80a0cee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -34,7 +34,6 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
@@ -155,12 +154,7 @@
                         if (!SceneContainerFlag.isEnabled) {
                             startTransitionTo(KeyguardState.GLANCEABLE_HUB)
                         }
-                    } else if (
-                        powerInteractor.detailedWakefulness.value.lastWakeReason ==
-                            WakeSleepReason.POWER_BUTTON &&
-                            isCommunalAvailable &&
-                            dreamManager.canStartDreaming(true)
-                    ) {
+                    } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
                         // This case handles tapping the power button to transition through
                         // dream -> off -> hub.
                         if (!SceneContainerFlag.isEnabled) {
@@ -226,14 +220,7 @@
                                     ownerReason = "waking from dozing"
                                 )
                             }
-                        } else if (
-                            powerInteractor.detailedWakefulness.value.lastWakeReason ==
-                                WakeSleepReason.POWER_BUTTON &&
-                                isCommunalAvailable &&
-                                dreamManager.canStartDreaming(true)
-                        ) {
-                            // This case handles tapping the power button to transition through
-                            // dream -> off -> hub.
+                        } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
                             if (!SceneContainerFlag.isEnabled) {
                                 transitionToGlanceableHub()
                             }
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 4cab2bb..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
@@ -19,6 +19,7 @@
 
 import android.app.StatusBarManager
 import android.graphics.Point
+import android.util.Log
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
@@ -35,9 +36,12 @@
 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
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
@@ -87,6 +91,7 @@
     sceneInteractorProvider: Provider<SceneInteractor>,
     private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>,
     private val fromLockscreenTransitionInteractor: Provider<FromLockscreenTransitionInteractor>,
+    private val fromOccludedTransitionInteractor: Provider<FromOccludedTransitionInteractor>,
     sharedNotificationContainerInteractor: Provider<SharedNotificationContainerInteractor>,
     @Application applicationScope: CoroutineScope,
 ) {
@@ -406,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.
@@ -484,7 +495,11 @@
 
     /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */
     fun dismissKeyguard() {
-        fromLockscreenTransitionInteractor.get().dismissKeyguard()
+        when (keyguardTransitionInteractor.transitionState.value.to) {
+            LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+            OCCLUDED -> fromOccludedTransitionInteractor.get().dismissFromOccluded()
+            else -> Log.v(TAG, "Keyguard was dismissed, no direct transition call needed")
+        }
     }
 
     fun onCameraLaunchDetected(source: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 2af95f2..2c3b481 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -119,7 +119,8 @@
                             is ObservableTransitionState.Idle ->
                                 it.currentScene == Scenes.Lockscreen
                             is ObservableTransitionState.Transition ->
-                                it.fromScene == Scenes.Lockscreen || it.toScene == Scenes.Lockscreen
+                                it.fromContent == Scenes.Lockscreen ||
+                                    it.toContent == Scenes.Lockscreen
                         }
                     }
                     .distinctUntilChanged()
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 cd49c6a..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
@@ -27,6 +27,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -47,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
@@ -67,14 +67,13 @@
     broadcastDispatcher: BroadcastDispatcher,
     private val accessibilityManager: AccessibilityManagerWrapper,
     private val pulsingGestureListener: PulsingGestureListener,
+    private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
 ) {
     /** Whether the long-press handling feature should be enabled. */
     val isLongPressHandlingEnabled: StateFlow<Boolean> =
         if (isFeatureEnabled()) {
                 combine(
-                    transitionInteractor.finishedKeyguardState.map {
-                        it == KeyguardState.LOCKSCREEN
-                    },
+                    transitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN),
                     repository.isQuickSettingsVisible,
                 ) { isFullyTransitionedToLockScreen, isQuickSettingsVisible ->
                     isFullyTransitionedToLockScreen && !isQuickSettingsVisible
@@ -129,7 +128,8 @@
         }
     }
 
-    /** Notifies that the user has long-pressed on the lock screen.
+    /**
+     * Notifies that the user has long-pressed on the lock screen.
      *
      * @param isA11yAction: Whether the action was performed as an a11y action
      */
@@ -174,6 +174,7 @@
     /** Notifies that the lockscreen has been clicked at position [x], [y]. */
     fun onClick(x: Float, y: Float) {
         pulsingGestureListener.onSingleTapUp(x, y)
+        faceAuthInteractor.onNotificationPanelClicked()
     }
 
     /** Notifies that the lockscreen has been double clicked. */
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..e19b72e 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,21 +22,17 @@
 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
 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.util.kotlin.WithPrev
 import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -47,7 +43,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 +51,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 +60,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>>()
@@ -104,6 +89,18 @@
     val transitionState: StateFlow<TransitionStep> =
         transitions.stateIn(scope, SharingStarted.Eagerly, TransitionStep())
 
+    private val sceneTransitionPair =
+        sceneInteractor.transitionState
+            .pairwise()
+            .stateIn(
+                scope,
+                SharingStarted.Eagerly,
+                WithPrev(
+                    sceneInteractor.transitionState.value,
+                    sceneInteractor.transitionState.value
+                )
+            )
+
     /**
      * A pair of the most recent STARTED step, and the transition step immediately preceding it. The
      * transition framework enforces that the previous step is either a CANCELED or FINISHED step,
@@ -126,8 +123,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 +182,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. */
@@ -202,7 +207,7 @@
             }
 
         return if (SceneContainerFlag.isEnabled) {
-            flow.filter {
+            flow.filter { step ->
                 val fromScene =
                     when (edge) {
                         is Edge.StateToState -> edge.from?.mapToSceneContainerScene()
@@ -219,8 +224,23 @@
 
                 fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null
 
-                return@filter (fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()) ||
+                val isTransitioningBetweenLockscreenStates =
+                    fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()
+                val isTransitioningBetweenDesiredScenes =
                     sceneInteractor.transitionState.value.isTransitioning(fromScene, toScene)
+
+                // We can't compare the terminal step with the current sceneTransition because
+                // a) STL has no guarantee that it will settle in Idle() when finished/canceled
+                // b) Comparing to Idle(toScene) would make any other FINISHED step settling in
+                //    toScene pass as well
+                val terminalStepBelongsToPreviousTransition =
+                    (step.transitionState == TransitionState.FINISHED ||
+                        step.transitionState == TransitionState.CANCELED) &&
+                        sceneTransitionPair.value.previousValue.isTransitioning(fromScene, toScene)
+
+                return@filter isTransitioningBetweenLockscreenStates ||
+                    isTransitioningBetweenDesiredScenes ||
+                    terminalStepBelongsToPreviousTransition
             }
         } else {
             flow
@@ -250,10 +270,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 +297,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 +366,7 @@
                     it.from
                 }
             }
-            .distinctUntilChanged()
-            .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+            .stateIn(scope, SharingStarted.Eagerly, OFF)
 
     val isInTransition =
         combine(
@@ -422,33 +378,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 +435,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 +450,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..a09cd7c 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
         }
 
     /**
@@ -124,8 +127,8 @@
                     when (transitionState) {
                         is ObservableTransitionState.Transition ->
                             when {
-                                transitionState.fromScene == Scenes.Lockscreen &&
-                                    transitionState.toScene == Scenes.Gone ->
+                                transitionState.fromContent == Scenes.Lockscreen &&
+                                    transitionState.toContent == Scenes.Gone ->
                                     sceneInteractor
                                         .get()
                                         .isTransitionUserInputOngoing
@@ -136,8 +139,8 @@
                                                 flowOf(true)
                                             }
                                         }
-                                transitionState.fromScene == Scenes.Bouncer &&
-                                    transitionState.toScene == Scenes.Gone ->
+                                transitionState.fromContent == Scenes.Bouncer &&
+                                    transitionState.toContent == Scenes.Gone ->
                                     transitionState.progress.map { progress ->
                                         progress >
                                             FromPrimaryBouncerTransitionInteractor
@@ -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..f3bb829 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
@@ -111,12 +111,12 @@
         if (currentTransitionId == null) return
         if (prevTransition !is ObservableTransitionState.Transition) return
 
-        if (idle.currentScene == prevTransition.toScene) {
+        if (idle.currentScene == prevTransition.toContent) {
             finishCurrentTransition()
         } else {
             val targetState =
                 if (idle.currentScene == Scenes.Lockscreen) {
-                    transitionInteractor.getStartedFromState()
+                    transitionInteractor.startedKeyguardTransitionStep.value.from
                 } else {
                     UNDEFINED
                 }
@@ -150,17 +150,17 @@
     }
 
     private suspend fun handleTransition(transition: ObservableTransitionState.Transition) {
-        if (transition.fromScene == Scenes.Lockscreen) {
+        if (transition.fromContent == Scenes.Lockscreen) {
             if (currentTransitionId != null) {
                 val currentToState =
                     internalTransitionInteractor.currentTransitionInfoInternal.value.to
                 if (currentToState == UNDEFINED) {
-                    transitionKtfTo(transitionInteractor.getStartedFromState())
+                    transitionKtfTo(transitionInteractor.startedKeyguardTransitionStep.value.from)
                 }
             }
             startTransitionFromLockscreen()
             collectProgress(transition)
-        } else if (transition.toScene == Scenes.Lockscreen) {
+        } else if (transition.toContent == Scenes.Lockscreen) {
             if (currentTransitionId != null) {
                 transitionKtfTo(UNDEFINED)
             }
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/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index bec8f3d..f1b9cba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -93,7 +93,7 @@
                                         blueprint.applyConstraints(this)
                                     }
 
-                                logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel)
+                                logAlphaVisibilityScaleOfAppliedConstraintSet(cs, clockViewModel)
                                 cs.applyTo(constraintLayout)
                             }
                         }
@@ -115,7 +115,7 @@
                                     clone(constraintLayout)
                                     blueprint.applyConstraints(this)
                                 }
-                            logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel)
+                            logAlphaVisibilityScaleOfAppliedConstraintSet(cs, clockViewModel)
                             cs.applyTo(constraintLayout)
                         }
                     }
@@ -124,7 +124,7 @@
         }
     }
 
-    private fun logAlphaVisibilityOfAppliedConstraintSet(
+    private fun logAlphaVisibilityScaleOfAppliedConstraintSet(
         cs: ConstraintSet,
         viewModel: KeyguardClockViewModel
     ) {
@@ -136,12 +136,15 @@
         Log.i(
             TAG,
             "applyCsToSmallClock: vis=${cs.getVisibility(smallClockViewId)} " +
-                "alpha=${cs.getConstraint(smallClockViewId).propertySet.alpha}"
+                "alpha=${cs.getConstraint(smallClockViewId).propertySet.alpha} " +
+                "scale=${cs.getConstraint(smallClockViewId).transform.scaleX} "
         )
         Log.i(
             TAG,
             "applyCsToLargeClock: vis=${cs.getVisibility(largeClockViewId)} " +
-                "alpha=${cs.getConstraint(largeClockViewId).propertySet.alpha}"
+                "alpha=${cs.getConstraint(largeClockViewId).propertySet.alpha} " +
+                "scale=${cs.getConstraint(largeClockViewId).transform.scaleX} " +
+                "pivotX=${cs.getConstraint(largeClockViewId).transform.transformPivotX} "
         )
         Log.i(
             TAG,
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..5bb7b64 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
@@ -40,6 +40,8 @@
 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.keyguard.AuthInteractionProperties
+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,8 @@
 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.MSDLPlayer
 import kotlin.math.min
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.DisposableHandle
@@ -112,6 +116,7 @@
         falsingManager: FalsingManager?,
         keyguardViewMediator: KeyguardViewMediator?,
         mainImmediateDispatcher: CoroutineDispatcher,
+        msdlPlayer: MSDLPlayer?,
     ): DisposableHandle {
         val disposables = DisposableHandles()
         val childViews = mutableMapOf<Int, View>()
@@ -351,21 +356,33 @@
                     if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
                         launch {
                             deviceEntryHapticsInteractor.playSuccessHaptic.collect {
-                                vibratorHelper.performHapticFeedback(
-                                    view,
-                                    HapticFeedbackConstants.CONFIRM,
-                                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
-                                )
+                                if (msdlFeedback()) {
+                                    msdlPlayer?.playToken(
+                                        MSDLToken.UNLOCK,
+                                        authInteractionProperties
+                                    )
+                                } else {
+                                    vibratorHelper.performHapticFeedback(
+                                        view,
+                                        HapticFeedbackConstants.BIOMETRIC_CONFIRM,
+                                    )
+                                }
                             }
                         }
 
                         launch {
                             deviceEntryHapticsInteractor.playErrorHaptic.collect {
-                                vibratorHelper.performHapticFeedback(
-                                    view,
-                                    HapticFeedbackConstants.REJECT,
-                                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
-                                )
+                                if (msdlFeedback()) {
+                                    msdlPlayer?.playToken(
+                                        MSDLToken.FAILURE,
+                                        authInteractionProperties
+                                    )
+                                } else {
+                                    vibratorHelper.performHapticFeedback(
+                                        view,
+                                        HapticFeedbackConstants.BIOMETRIC_REJECT,
+                                    )
+                                }
                             }
                         }
                     }
@@ -634,6 +651,7 @@
     private val lockIcon = R.id.lock_icon_view
     private val deviceEntryIcon = R.id.device_entry_icon_view
     private val nsslPlaceholderId = R.id.nssl_placeholder
+    private val authInteractionProperties = AuthInteractionProperties()
 
     private const val ID = "occluding_app_device_entry_unlock_msg"
     private const val AOD_ICONS_APPEAR_DURATION: Long = 200
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 51ce355..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
@@ -79,6 +79,7 @@
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.monet.Style
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.clocks.ClockRegistry
@@ -188,6 +189,7 @@
     init {
         coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
         disposables += DisposableHandle { coroutineScope.cancel() }
+        clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
 
         if (KeyguardBottomAreaRefactor.isEnabled) {
             quickAffordancesCombinedViewModel.enablePreviewMode(
@@ -416,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/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index c8fe55d..be6b0eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -59,6 +59,16 @@
     alpha: Float,
 ) = views.forEach { view -> this.setAlpha(view.id, alpha) }
 
+internal fun ConstraintSet.setScaleX(
+    views: Iterable<View>,
+    alpha: Float,
+) = views.forEach { view -> this.setScaleX(view.id, alpha) }
+
+internal fun ConstraintSet.setScaleY(
+    views: Iterable<View>,
+    alpha: Float,
+) = views.forEach { view -> this.setScaleY(view.id, alpha) }
+
 @SysUISingleton
 class ClockSection
 @Inject
@@ -125,6 +135,9 @@
             setAlpha(getNonTargetClockFace(clock).views, 0F)
             if (!keyguardClockViewModel.isLargeClockVisible.value) {
                 connect(sharedR.id.bc_smartspace_view, TOP, sharedR.id.date_smartspace_view, BOTTOM)
+            } else {
+                setScaleX(getTargetClockFace(clock).views, rootViewModel.burnInModel.value.scale)
+                setScaleY(getTargetClockFace(clock).views, rootViewModel.burnInModel.value.scale)
             }
         }
     }
@@ -205,6 +218,9 @@
             create(R.id.small_clock_guideline_top, ConstraintSet.HORIZONTAL_GUIDELINE)
             setGuidelineBegin(R.id.small_clock_guideline_top, smallClockTopMargin)
             connect(R.id.lockscreen_clock_view, TOP, R.id.small_clock_guideline_top, BOTTOM)
+
+            // Explicitly clear pivot to force recalculate pivot instead of using legacy value
+            setTransformPivot(R.id.lockscreen_clock_view_large, Float.NaN, Float.NaN)
         }
 
         constrainWeatherClockDateIconsBarrier(constraints)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 55fc718..99160f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -127,6 +127,7 @@
 
             // migrate addSmartspaceView from KeyguardClockSwitchController
             constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
+            constrainWidth(sharedR.id.bc_smartspace_view, ConstraintSet.MATCH_CONSTRAINT)
             connect(
                 sharedR.id.bc_smartspace_view,
                 ConstraintSet.START,
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..a021de4 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
@@ -43,21 +43,21 @@
     val deviceEntryIconViewModel: DeviceEntryIconViewModel,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     configurationInteractor: ConfigurationInteractor,
-    lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
-    aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
-    goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
-    primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
-    occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
-    occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
-    dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
     alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
-    goneToLockscreenTransitionViewModel: GoneToLockscreenTransitionViewModel,
-    goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
-    primaryBouncerToDozingTransitionViewModel: PrimaryBouncerToDozingTransitionViewModel,
-    lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
-    dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
     alternateBouncerToDozingTransitionViewModel: AlternateBouncerToDozingTransitionViewModel,
+    aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+    dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
     dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
+    dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+    goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+    goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
+    goneToLockscreenTransitionViewModel: GoneToLockscreenTransitionViewModel,
+    lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
+    occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
+    occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel,
+    occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+    primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
+    primaryBouncerToDozingTransitionViewModel: PrimaryBouncerToDozingTransitionViewModel,
     primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
 ) {
     val color: Flow<Int> =
@@ -97,12 +97,12 @@
                         goneToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
                         goneToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
                         primaryBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
-                        lockscreenToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
                         dozingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
                         alternateBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
                         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/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index 0a84886..6579ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.content.Context
+import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.res.R
@@ -81,7 +82,7 @@
                 getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
             } else {
                 getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
-                    getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
+                    SystemBarUtils.getStatusBarHeight(context) +
                     getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
             }
         }
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..72740d5 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
@@ -20,7 +20,6 @@
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.FlowTracing.traceEmissionCount
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.NewPickerUiKeyguardPreview
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,6 +28,7 @@
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.Flags
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
@@ -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 =
@@ -171,7 +169,7 @@
 
     /** An observable for the view-model of the "start button" quick affordance. */
     val startButton: Flow<KeyguardQuickAffordanceViewModel> =
-        if (NewPickerUiKeyguardPreview.isEnabled) {
+        if (Flags.newCustomizationPickerUi()) {
             previewAffordances.flatMapLatestConflated {
                 button(
                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
@@ -186,7 +184,7 @@
 
     /** An observable for the view-model of the "end button" quick affordance. */
     val endButton: Flow<KeyguardQuickAffordanceViewModel> =
-        if (NewPickerUiKeyguardPreview.isEnabled) {
+        if (Flags.newCustomizationPickerUi()) {
             previewAffordances.flatMapLatestConflated {
                 button(
                     position = KeyguardQuickAffordancePosition.BOTTOM_END,
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 06f77bf..eaa61a1 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
@@ -33,15 +33,16 @@
 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.DREAMING
 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.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.ui.viewmodel.NotificationShadeWindowModel
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -83,6 +84,7 @@
     private val communalInteractor: CommunalInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+    notificationShadeWindowModel: NotificationShadeWindowModel,
     private val alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
     private val alternateBouncerToGoneTransitionViewModel:
         AlternateBouncerToGoneTransitionViewModel,
@@ -129,8 +131,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 =
@@ -159,30 +161,26 @@
 
     private val alphaOnShadeExpansion: Flow<Float> =
         combineTransform(
-                keyguardTransitionInteractor.isInTransition(
-                    edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
-                    edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
-                ),
-                keyguardTransitionInteractor.isInTransition(
-                    edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN),
-                    edgeWithoutSceneContainer =
-                        Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
+                anyOf(
+                    keyguardTransitionInteractor.isInTransition(
+                        edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone),
+                        edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE),
+                    ),
+                    keyguardTransitionInteractor.isInTransition(
+                        edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN),
+                        edgeWithoutSceneContainer =
+                            Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
+                    ),
+                    keyguardTransitionInteractor.isInTransition(
+                        Edge.create(from = LOCKSCREEN, to = DREAMING)
+                    ),
                 ),
                 isOnLockscreen,
                 shadeInteractor.qsExpansion,
                 shadeInteractor.shadeExpansion,
-            ) {
-                lockscreenToGoneTransitionRunning,
-                primaryBouncerToLockscreenTransitionRunning,
-                isOnLockscreen,
-                qsExpansion,
-                shadeExpansion ->
+            ) { disabledTransitionRunning, isOnLockscreen, qsExpansion, shadeExpansion ->
                 // Fade out quickly as the shade expands
-                if (
-                    isOnLockscreen &&
-                        !lockscreenToGoneTransitionRunning &&
-                        !primaryBouncerToLockscreenTransitionRunning
-                ) {
+                if (isOnLockscreen && !disabledTransitionRunning) {
                     val alpha =
                         1f -
                             MathUtils.constrainedMap(
@@ -198,29 +196,18 @@
             .distinctUntilChanged()
 
     /**
-     * Keyguard should not show while the communal hub is fully visible. This check is added since
-     * at the moment, closing the notification shade will cause the keyguard alpha to be set back to
-     * 1. Also ensure keyguard is never visible when GONE.
+     * Keyguard should not show if fully transitioned into a hidden keyguard state or if
+     * transitioning between hidden states.
      */
     private val hideKeyguard: Flow<Boolean> =
-        combine(
-                communalInteractor.isIdleOnCommunal,
-                keyguardTransitionInteractor
-                    .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
-                    .map { it == 1f }
-                    .onStart { emit(false) },
-                keyguardTransitionInteractor
-                    .transitionValue(OCCLUDED)
-                    .map { it == 1f }
-                    .onStart { emit(false) },
-                keyguardTransitionInteractor
-                    .transitionValue(KeyguardState.DREAMING)
-                    .map { it == 1f }
-                    .onStart { emit(false) },
-            ) { isIdleOnCommunal, isGone, isOccluded, isDreaming ->
-                isIdleOnCommunal || isGone || isOccluded || isDreaming
-            }
-            .distinctUntilChanged()
+        anyOf(
+            notificationShadeWindowModel.isKeyguardOccluded,
+            communalInteractor.isIdleOnCommunal,
+            keyguardTransitionInteractor
+                .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
+                .map { it == 1f }
+                .onStart { emit(false) },
+        )
 
     /** Last point that the root view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
@@ -326,16 +313,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>
@@ -345,8 +333,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 666c9f8..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
@@ -20,10 +20,11 @@
 import com.android.compose.animation.scene.ContentKey
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 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
@@ -39,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
@@ -57,7 +57,8 @@
     private val shadeInteractor: ShadeInteractor,
     private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
     private val occlusionInteractor: SceneContainerOcclusionInteractor,
-) : SysUiViewModel() {
+    private val deviceEntryInteractor: DeviceEntryInteractor,
+) : ExclusiveActivatable() {
     @VisibleForTesting val clockSize = clockInteractor.clockSize
 
     val isUdfpsVisible: Boolean
@@ -73,6 +74,10 @@
     /** Whether the content of the scene UI should be shown. */
     val isContentVisible: StateFlow<Boolean> = _isContentVisible.asStateFlow()
 
+    /** @see DeviceEntryInteractor.isBypassEnabled */
+    val isBypassEnabled: StateFlow<Boolean>
+        get() = deviceEntryInteractor.isBypassEnabled
+
     override suspend fun onActivated(): Nothing {
         coroutineScope {
             launch {
@@ -85,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/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index 27a1f7a..d3eefca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -45,13 +45,7 @@
             edge = Edge.create(from = LOCKSCREEN, to = DOZING),
         )
 
-    val lockscreenAlpha: Flow<Float> =
-        transitionAnimation.sharedFlow(
-            duration = 250.milliseconds,
-            onStep = { 1 - it },
-            onFinish = { 1f },
-            onCancel = { 1f },
-        )
+    val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f)
 
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
@@ -61,18 +55,16 @@
             onCancel = { 1f },
         )
 
-    val deviceEntryBackgroundViewAlpha: Flow<Float> =
-        transitionAnimation.immediatelyTransitionTo(0f)
-
     override val deviceEntryParentViewAlpha: Flow<Float> =
         deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
             isUdfpsEnrolledAndEnabled ->
-            transitionAnimation.immediatelyTransitionTo(
-                if (isUdfpsEnrolledAndEnabled) {
-                    1f
-                } else {
-                    0f
-                }
-            )
+            if (isUdfpsEnrolledAndEnabled) {
+                transitionAnimation.immediatelyTransitionTo(1f)
+            } else {
+                transitionAnimation.sharedFlow(
+                    duration = 250.milliseconds,
+                    onStep = { 1f - it },
+                )
+            }
         }
 }
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/LockscreenSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
index 7383f57..dd47678 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
@@ -28,26 +28,25 @@
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.util.kotlin.filterValuesNotNull
 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
 
 /** Models UI state and handles user input for the lockscreen scene. */
-class LockscreenSceneActionsViewModel
+class LockscreenUserActionsViewModel
 @AssistedInject
 constructor(
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val communalInteractor: CommunalInteractor,
     private val shadeInteractor: ShadeInteractor,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
         shadeInteractor.isShadeTouchable
@@ -100,7 +99,7 @@
                     }
                 }
             }
-            .collectLatest { setActions(it) }
+            .collect { setActions(it) }
     }
 
     private fun swipeDownFromTop(pointerCount: Int): Swipe {
@@ -120,6 +119,6 @@
 
     @AssistedFactory
     interface Factory {
-        fun create(): LockscreenSceneActionsViewModel
+        fun create(): LockscreenUserActionsViewModel
     }
 }
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/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 2089cce5..89a599a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -16,19 +16,17 @@
 
 package com.android.systemui.log.table
 
+import android.annotation.SuppressLint
 import android.icu.text.SimpleDateFormat
 import android.os.Trace
 import com.android.systemui.Dumpable
 import com.android.systemui.common.buffer.RingBuffer
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.log.TableLogBufferBase
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import java.util.Locale
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
 
 /**
  * A logger that logs changes in table format.
@@ -75,13 +73,12 @@
  *
  * @param maxSize the maximum size of the buffer. Must be > 0.
  */
+@SuppressLint("DumpableNotRegistered") // Registered as dumpable in [TableLogBufferFactory]
 class TableLogBuffer(
     maxSize: Int,
     private val name: String,
     private val systemClock: SystemClock,
     private val logcatEchoTracker: LogcatEchoTracker,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    private val coroutineScope: CoroutineScope,
     private val localLogcat: LogProxy = LogProxyDefault(),
 ) : Dumpable, TableLogBufferBase {
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
index ff523ae..425e674e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -17,15 +17,11 @@
 package com.android.systemui.log.table
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
 import com.android.systemui.log.LogcatEchoTracker
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
 
 @SysUISingleton
 class TableLogBufferFactory
@@ -34,8 +30,6 @@
     private val dumpManager: DumpManager,
     private val systemClock: SystemClock,
     private val logcatEchoTracker: LogcatEchoTracker,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    @Application private val coroutineScope: CoroutineScope,
 ) {
     private val existingBuffers = mutableMapOf<String, TableLogBuffer>()
 
@@ -58,8 +52,6 @@
                 name,
                 systemClock,
                 logcatEchoTracker,
-                bgDispatcher,
-                coroutineScope,
             )
         dumpManager.registerTableLogBuffer(name, tableBuffer)
         return tableBuffer
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/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 143d66b..24c57be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.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
@@ -62,8 +60,10 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 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.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
@@ -86,7 +86,6 @@
 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.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.notification.row.HybridGroupManager
 import com.android.systemui.tuner.TunerService
@@ -97,8 +96,13 @@
 import com.android.systemui.util.time.SystemClock
 import java.io.IOException
 import java.io.PrintWriter
+import java.util.Collections
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 // URI fields to try loading album art from
 private val ART_URIS =
@@ -167,8 +171,11 @@
 class LegacyMediaDataManagerImpl(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     @Main private val uiExecutor: Executor,
     @Main private val foregroundExecutor: DelayableExecutor,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Application private val applicationScope: CoroutineScope,
     private val mediaControllerFactory: MediaControllerFactory,
     private val broadcastDispatcher: BroadcastDispatcher,
     dumpManager: DumpManager,
@@ -188,6 +195,7 @@
     private val logger: MediaUiEventLogger,
     private val smartspaceManager: SmartspaceManager?,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
 
     companion object {
@@ -219,7 +227,12 @@
     // listeners are listeners that depend on MediaDataManager.
     // TODO(b/159539991#comment5): Move internal listeners to separate package.
     private val internalListeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
-    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+    private val mediaEntries: MutableMap<String, MediaData> =
+        if (Flags.mediaLoadMetadataViaMediaDataLoader()) {
+            Collections.synchronizedMap(LinkedHashMap())
+        } else {
+            LinkedHashMap()
+        }
     // There should ONLY be at most one Smartspace media recommendation.
     var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
     @Keep private var smartspaceSession: SmartspaceSession? = null
@@ -245,8 +258,11 @@
     constructor(
         context: Context,
         threadFactory: ThreadFactory,
+        @Background backgroundDispatcher: CoroutineDispatcher,
         @Main uiExecutor: Executor,
         @Main foregroundExecutor: DelayableExecutor,
+        @Main mainDispatcher: CoroutineDispatcher,
+        @Application applicationScope: CoroutineScope,
         mediaControllerFactory: MediaControllerFactory,
         dumpManager: DumpManager,
         broadcastDispatcher: BroadcastDispatcher,
@@ -264,13 +280,17 @@
         logger: MediaUiEventLogger,
         smartspaceManager: SmartspaceManager?,
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        mediaDataLoader: dagger.Lazy<MediaDataLoader>,
     ) : this(
         context,
         // Loading bitmap for UMO background can take longer time, so it cannot run on the default
         // background thread. Use a custom thread for media.
         threadFactory.buildExecutorOnNewThread(TAG),
+        backgroundDispatcher,
         uiExecutor,
         foregroundExecutor,
+        mainDispatcher,
+        applicationScope,
         mediaControllerFactory,
         broadcastDispatcher,
         dumpManager,
@@ -290,6 +310,7 @@
         logger,
         smartspaceManager,
         keyguardUpdateMonitor,
+        mediaDataLoader,
     )
 
     private val appChangeReceiver =
@@ -464,16 +485,31 @@
             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
+                )
+            }
         }
     }
 
@@ -498,9 +534,90 @@
         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) }
+        }
     }
 
+    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 = mediaEntries[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,
+                    )
+                )
+            }
+        }
+
     /** Add a listener for changes in this class */
     override fun addListener(listener: MediaDataManager.Listener) {
         // mediaDataFilter is the current end of the internal pipeline. Register external
@@ -697,6 +814,75 @@
         notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
     }
 
+    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 = mediaEntries[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")
+                mediaEntries.remove(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,
@@ -780,6 +966,7 @@
         }
     }
 
+    @Deprecated("Cleanup when media_load_metadata_via_media_data_loader is cleaned up")
     fun loadMediaDataInBg(
         key: String,
         sbn: StatusBarNotification,
@@ -802,8 +989,7 @@
             notif.extras.getParcelable(
                 Notification.EXTRA_BUILDER_APPLICATION_INFO,
                 ApplicationInfo::class.java
-            )
-                ?: getAppInfoFromPackage(sbn.packageName)
+            ) ?: getAppInfoFromPackage(sbn.packageName)
 
         // App name
         val appName = getAppName(sbn, appInfo)
@@ -894,7 +1080,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
         }
@@ -975,6 +1161,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)
@@ -984,6 +1171,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) {
@@ -997,264 +1185,19 @@
         }
     }
 
-    /** 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
-     *
-     * @param packageName Package name for the media app
-     * @param controller MediaController for the current session
-     * @return a Pair consisting of a list of media actions, and a list of ints representing which
-     *
-     * ```
-     *      of those actions should be shown in the compact player
-     * ```
-     */
     private fun createActionsFromState(
         packageName: String,
         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(state, 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(
-        state: PlaybackState,
-        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)
@@ -1269,21 +1212,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,
@@ -1364,6 +1292,7 @@
         )
     }
 
+    @MainThread
     fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) =
         traceSection("MediaDataManager#onMediaDataLoaded") {
             Assert.isMainThread()
@@ -1619,6 +1548,7 @@
      * - If resumption is disabled, we only want to show active players
      */
     override fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
+
     override fun isRecommendationActive() = smartspaceMediaData.isActive
 
     /**
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
new file mode 100644
index 0000000..bcf748e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -0,0 +1,312 @@
+/*
+ * 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.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Icon
+import android.media.session.MediaController
+import android.media.session.PlaybackState
+import android.service.notification.StatusBarNotification
+import android.util.Log
+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
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
+import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.util.kotlin.logI
+
+private const val TAG = "MediaActions"
+
+/**
+ * Generates action button info for this media session based on the PlaybackState
+ *
+ * @param packageName Package name for the media app
+ * @param controller MediaController for the current session
+ * @return a Pair consisting of a list of media actions, and a list of ints representing which of
+ *   those actions should be shown in the compact player
+ */
+fun createActionsFromState(
+    context: Context,
+    packageName: String,
+    controller: MediaController,
+): MediaButton? {
+    val state = controller.playbackState ?: 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(context, controller, state.actions, PlaybackState.ACTION_PAUSE)
+        } else {
+            getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PLAY)
+        }
+    val prevButton =
+        getStandardAction(context, controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+    val nextButton =
+        getStandardAction(context, 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(context, 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(
+    context: Context,
+    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(
+                MediaControlDrawables.getPrevIcon(context),
+                { controller.transportControls.skipToPrevious() },
+                context.getString(R.string.controls_media_button_prev),
+                null
+            )
+        }
+        PlaybackState.ACTION_SKIP_TO_NEXT -> {
+            MediaAction(
+                MediaControlDrawables.getNextIcon(context),
+                { controller.transportControls.skipToNext() },
+                context.getString(R.string.controls_media_button_next),
+                null
+            )
+        }
+        else -> null
+    }
+}
+
+/** Get a [MediaAction] representing a [PlaybackState.CustomAction] */
+private fun getCustomAction(
+    context: Context,
+    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
+    )
+}
+
+/** 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)
+}
+
+/** Generate action buttons based on notification actions */
+fun createActionsFromNotification(
+    context: Context,
+    activityStarter: ActivityStarter,
+    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)
+    }
+
+    actions?.let {
+        if (it.size > MAX_NOTIFICATION_ACTIONS) {
+            Log.w(
+                TAG,
+                "Too many notification actions for ${sbn.key}, " +
+                    "limiting to first $MAX_NOTIFICATION_ACTIONS"
+            )
+        }
+
+        for ((index, action) in it.take(MAX_NOTIFICATION_ACTIONS).withIndex()) {
+            if (action.getIcon() == null) {
+                logI(TAG) { "No icon for action $index ${action.title}" }
+                actionsToShowCollapsed.remove(index)
+                continue
+            }
+
+            val runnable =
+                action.actionIntent?.let { actionIntent ->
+                    Runnable {
+                        when {
+                            actionIntent.isActivity ->
+                                activityStarter.startPendingIntentDismissingKeyguard(
+                                    action.actionIntent
+                                )
+                            action.isAuthenticationRequired ->
+                                activityStarter.dismissKeyguardThenExecute(
+                                    { sendPendingIntent(action.actionIntent) },
+                                    {},
+                                    true
+                                )
+                            else -> sendPendingIntent(actionIntent)
+                        }
+                    }
+                }
+
+            val themeText =
+                com.android.settingslib.Utils.getColorAttr(
+                        context,
+                        com.android.internal.R.attr.textColorPrimary
+                    )
+                    .defaultColor
+
+            val mediaActionIcon =
+                when (action.getIcon().type) {
+                        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)
+}
+
+private fun sendPendingIntent(intent: PendingIntent): Boolean {
+    return try {
+        intent.send(
+            BroadcastOptions.makeBasic()
+                .apply {
+                    setInteractive(true)
+                    setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                    )
+                }
+                .toBundle()
+        )
+        true
+    } catch (e: PendingIntent.CanceledException) {
+        Log.d(TAG, "Intent canceled", e)
+        false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
new file mode 100644
index 0000000..f9fef8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -0,0 +1,530 @@
+/*
+ * 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.annotation.WorkerThread
+import android.app.Notification
+import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
+import android.app.PendingIntent
+import android.app.StatusBarManager
+import android.app.UriGrantsManager
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.drawable.Icon
+import android.media.MediaDescription
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.net.Uri
+import android.os.Process
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import android.support.v4.media.MediaMetadataCompat
+import android.text.TextUtils
+import android.util.Log
+import android.util.Pair
+import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.traceCoroutine
+import com.android.systemui.dagger.SysUISingleton
+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.graphics.ImageLoader
+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.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
+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.NotificationMediaManager.isPlayingState
+import com.android.systemui.statusbar.notification.row.HybridGroupManager
+import com.android.systemui.util.kotlin.logD
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlin.coroutines.coroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.ensureActive
+
+/** Loads media information from media style [StatusBarNotification] classes. */
+@SysUISingleton
+class MediaDataLoader
+@Inject
+constructor(
+    @Application val context: Context,
+    @Main val mainDispatcher: CoroutineDispatcher,
+    @Background val backgroundScope: CoroutineScope,
+    private val activityStarter: ActivityStarter,
+    private val mediaControllerFactory: MediaControllerFactory,
+    private val mediaFlags: MediaFlags,
+    private val imageLoader: ImageLoader,
+    private val statusBarManager: StatusBarManager,
+) {
+    private val mediaProcessingJobs = ConcurrentHashMap<JobKey, Job>()
+
+    private val artworkWidth: Int =
+        context.resources.getDimensionPixelSize(
+            com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize
+        )
+    private val artworkHeight: Int =
+        context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+
+    private val themeText =
+        com.android.settingslib.Utils.getColorAttr(
+                context,
+                com.android.internal.R.attr.textColorPrimary
+            )
+            .defaultColor
+
+    /**
+     * Loads media data for a given [StatusBarNotification]. It does the loading on the background
+     * thread.
+     *
+     * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed. The method
+     * suspends until loading has completed or failed.
+     *
+     * If a new [loadMediaData] is issued while existing load is in progress, the existing (old)
+     * load will be cancelled.
+     */
+    suspend fun loadMediaData(key: String, sbn: StatusBarNotification): MediaDataLoaderResult? {
+        logD(TAG) { "Loading media data for $key..." }
+        val jobKey = JobKey(key, sbn)
+        val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn) }
+        loadMediaJob.invokeOnCompletion { mediaProcessingJobs.remove(jobKey) }
+        val existingJob = mediaProcessingJobs.put(jobKey, loadMediaJob)
+        existingJob?.cancel("New processing job incoming.")
+        return loadMediaJob.await()
+    }
+
+    /** Loads media data, should be called from [backgroundScope]. */
+    @WorkerThread
+    private suspend fun loadMediaDataInBackground(
+        key: String,
+        sbn: StatusBarNotification,
+    ): MediaDataLoaderResult? =
+        traceCoroutine("MediaDataLoader#loadMediaData") {
+            val token =
+                sbn.notification.extras.getParcelable(
+                    Notification.EXTRA_MEDIA_SESSION,
+                    MediaSession.Token::class.java
+                )
+            if (token == null) {
+                Log.i(TAG, "Token was null, not loading media info")
+                return null
+            }
+            val mediaController = mediaControllerFactory.create(token)
+            val metadata = mediaController.metadata
+            val notification: Notification = sbn.notification
+
+            val appInfo =
+                notification.extras.getParcelable(
+                    Notification.EXTRA_BUILDER_APPLICATION_INFO,
+                    ApplicationInfo::class.java
+                ) ?: getAppInfoFromPackage(sbn.packageName)
+
+            // App name
+            val appName = getAppName(sbn, appInfo)
+
+            // Song name
+            var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+            if (song.isNullOrBlank()) {
+                song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+            }
+            if (song.isNullOrBlank()) {
+                song = HybridGroupManager.resolveTitle(notification)
+            }
+            if (song.isNullOrBlank()) {
+                // For apps that don't include a title, log and add a placeholder
+                song = context.getString(R.string.controls_media_empty_title, appName)
+                try {
+                    statusBarManager.logBlankMediaTitle(sbn.packageName, sbn.user.identifier)
+                } catch (e: RuntimeException) {
+                    Log.e(TAG, "Error reporting blank media title for package ${sbn.packageName}")
+                }
+            }
+
+            // Don't attempt to load bitmaps if the job was cancelled.
+            coroutineContext.ensureActive()
+
+            // Album art
+            var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
+            if (artworkBitmap == null) {
+                artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
+            }
+            if (artworkBitmap == null) {
+                artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+            }
+            val artworkIcon =
+                if (artworkBitmap == null) {
+                    notification.getLargeIcon()
+                } else {
+                    Icon.createWithBitmap(artworkBitmap)
+                }
+
+            // Don't continue if we were cancelled during slow bitmap load.
+            coroutineContext.ensureActive()
+
+            // App Icon
+            val smallIcon = sbn.notification.smallIcon
+
+            // Explicit Indicator
+            val isExplicit =
+                MediaMetadataCompat.fromMediaMetadata(metadata)
+                    ?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+                    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+            // Artist name
+            var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
+            if (artist.isNullOrBlank()) {
+                artist = HybridGroupManager.resolveText(notification)
+            }
+
+            // Device name (used for remote cast notifications)
+            val device: MediaDeviceData? = getDeviceInfoForRemoteCast(key, sbn)
+
+            // Control buttons
+            // If flag is enabled and controller has a PlaybackState, create actions from session
+            // info
+            // Otherwise, use the notification actions
+            var actionIcons: List<MediaAction> = emptyList()
+            var actionsToShowCollapsed: List<Int> = emptyList()
+            val semanticActions = createActionsFromState(sbn.packageName, mediaController, sbn.user)
+            logD(TAG) { "Semantic actions: $semanticActions" }
+            if (semanticActions == null) {
+                val actions = createActionsFromNotification(context, activityStarter, sbn)
+                actionIcons = actions.first
+                actionsToShowCollapsed = actions.second
+                logD(TAG) { "[!!] Semantic actions: $semanticActions" }
+            }
+
+            val playbackLocation = getPlaybackLocation(sbn, mediaController)
+            val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) }
+
+            val appUid = appInfo?.uid ?: Process.INVALID_UID
+            return MediaDataLoaderResult(
+                appName = appName,
+                appIcon = smallIcon,
+                artist = artist,
+                song = song,
+                artworkIcon = artworkIcon,
+                actionIcons = actionIcons,
+                actionsToShowInCompact = actionsToShowCollapsed,
+                semanticActions = semanticActions,
+                token = token,
+                clickIntent = notification.contentIntent,
+                device = device,
+                playbackLocation = playbackLocation,
+                isPlaying = isPlaying,
+                appUid = appUid,
+                isExplicit = isExplicit
+            )
+        }
+
+    /**
+     * Loads media data in background for a given set of resumption parameters. The method suspends
+     * until loading is complete or fails.
+     *
+     * Returns a [MediaDataLoaderResult] if loaded data or `null` if loading failed.
+     */
+    suspend fun loadMediaDataForResumption(
+        userId: Int,
+        desc: MediaDescription,
+        resumeAction: Runnable,
+        currentEntry: MediaData?,
+        token: MediaSession.Token,
+        appName: String,
+        appIntent: PendingIntent,
+        packageName: String
+    ): MediaDataLoaderResult? {
+        val mediaData =
+            backgroundScope.async {
+                loadMediaDataForResumptionInBackground(
+                    userId,
+                    desc,
+                    resumeAction,
+                    currentEntry,
+                    token,
+                    appName,
+                    appIntent,
+                    packageName
+                )
+            }
+        return mediaData.await()
+    }
+
+    /** Loads media data for resumption, should be called from [backgroundScope]. */
+    @WorkerThread
+    private suspend fun loadMediaDataForResumptionInBackground(
+        userId: Int,
+        desc: MediaDescription,
+        resumeAction: Runnable,
+        currentEntry: MediaData?,
+        token: MediaSession.Token,
+        appName: String,
+        appIntent: PendingIntent,
+        packageName: String
+    ): MediaDataLoaderResult? =
+        traceCoroutine("MediaDataLoader#loadMediaDataForResumption") {
+            if (desc.title.isNullOrBlank()) {
+                Log.e(TAG, "Description incomplete")
+                return null
+            }
+
+            logD(TAG) { "adding track for $userId from browser: $desc" }
+
+            val appUid = currentEntry?.appUid ?: Process.INVALID_UID
+
+            // Album art
+            var artworkBitmap = desc.iconBitmap
+            if (artworkBitmap == null && desc.iconUri != null) {
+                artworkBitmap =
+                    loadBitmapFromUriForUser(desc.iconUri!!, userId, appUid, packageName)
+            }
+            val artworkIcon =
+                if (artworkBitmap != null) {
+                    Icon.createWithBitmap(artworkBitmap)
+                } else {
+                    null
+                }
+
+            val isExplicit =
+                desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) ==
+                    MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
+
+            val progress =
+                if (mediaFlags.isResumeProgressEnabled()) {
+                    MediaDataUtils.getDescriptionProgress(desc.extras)
+                } else null
+
+            val mediaAction = getResumeMediaAction(resumeAction)
+            return MediaDataLoaderResult(
+                appName = appName,
+                appIcon = null,
+                artist = desc.subtitle,
+                song = desc.title,
+                artworkIcon = artworkIcon,
+                actionIcons = listOf(mediaAction),
+                actionsToShowInCompact = listOf(0),
+                semanticActions = MediaButton(playOrPause = mediaAction),
+                token = token,
+                clickIntent = appIntent,
+                device = null,
+                playbackLocation = 0,
+                isPlaying = null,
+                appUid = appUid,
+                isExplicit = isExplicit,
+                resumeAction = resumeAction,
+                resumeProgress = progress
+            )
+        }
+
+    private fun createActionsFromState(
+        packageName: String,
+        controller: MediaController,
+        user: UserHandle
+    ): MediaButton? {
+        if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
+            return null
+        }
+
+        return createActionsFromState(context, packageName, controller)
+    }
+
+    private fun getPlaybackLocation(sbn: StatusBarNotification, mediaController: MediaController) =
+        when {
+            isRemoteCastNotification(sbn) -> MediaData.PLAYBACK_CAST_REMOTE
+            mediaController.playbackInfo?.playbackType ==
+                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> MediaData.PLAYBACK_LOCAL
+            else -> MediaData.PLAYBACK_CAST_LOCAL
+        }
+
+    /**
+     * Returns [MediaDeviceData] if the [StatusBarNotification] is a remote cast notification.
+     * `null` otherwise.
+     */
+    private fun getDeviceInfoForRemoteCast(
+        key: String,
+        sbn: StatusBarNotification
+    ): MediaDeviceData? {
+        val extras = sbn.notification.extras
+        val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null)
+        val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1)
+        val deviceIntent =
+            extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, PendingIntent::class.java)
+        logD(TAG) { "$key is RCN for $deviceName" }
+
+        if (deviceName != null && deviceIcon > -1) {
+            // Name and icon must be present, but intent may be null
+            val enabled = deviceIntent != null && deviceIntent.isActivity
+            val deviceDrawable =
+                Icon.createWithResource(sbn.packageName, deviceIcon)
+                    .loadDrawable(sbn.getPackageContext(context))
+            return MediaDeviceData(
+                enabled,
+                deviceDrawable,
+                deviceName,
+                deviceIntent,
+                showBroadcastButton = false
+            )
+        }
+        return null
+    }
+
+    private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
+        try {
+            return context.packageManager.getApplicationInfo(packageName, 0)
+        } catch (e: PackageManager.NameNotFoundException) {
+            Log.w(TAG, "Could not get app info for $packageName", e)
+            return null
+        }
+    }
+
+    private fun getAppName(sbn: StatusBarNotification, appInfo: ApplicationInfo?): String {
+        val name = sbn.notification.extras.getString(EXTRA_SUBSTITUTE_APP_NAME)
+        return when {
+            name != null -> name
+            appInfo != null -> context.packageManager.getApplicationLabel(appInfo).toString()
+            else -> sbn.packageName
+        }
+    }
+
+    /** Load a bitmap from the various Art metadata URIs */
+    private suspend fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? {
+        for (uri in ART_URIS) {
+            val uriString = metadata.getString(uri)
+            if (!TextUtils.isEmpty(uriString)) {
+                val albumArt = loadBitmapFromUri(Uri.parse(uriString))
+                // If we got cancelled during slow album art load, cancel the rest of
+                // the process.
+                coroutineContext.ensureActive()
+                if (albumArt != null) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "loaded art from $uri")
+                    }
+                    return albumArt
+                }
+            }
+        }
+        return null
+    }
+
+    private suspend fun loadBitmapFromUri(uri: Uri): Bitmap? {
+        // ImageDecoder requires a scheme of the following types
+        if (
+            uri.scheme !in
+                listOf(
+                    ContentResolver.SCHEME_CONTENT,
+                    ContentResolver.SCHEME_ANDROID_RESOURCE,
+                    ContentResolver.SCHEME_FILE
+                )
+        ) {
+            Log.w(TAG, "Invalid album art uri $uri")
+            return null
+        }
+
+        val source = ImageLoader.Uri(uri)
+        return imageLoader.loadBitmap(
+            source,
+            artworkWidth,
+            artworkHeight,
+            allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+        )
+    }
+
+    private suspend fun loadBitmapFromUriForUser(
+        uri: Uri,
+        userId: Int,
+        appUid: Int,
+        packageName: String
+    ): Bitmap? {
+        try {
+            val ugm = UriGrantsManager.getService()
+            ugm.checkGrantUriPermission_ignoreNonSystem(
+                appUid,
+                packageName,
+                ContentProvider.getUriWithoutUserId(uri),
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                ContentProvider.getUserIdFromUri(uri, userId)
+            )
+            return loadBitmapFromUri(uri)
+        } catch (e: SecurityException) {
+            Log.e(TAG, "Failed to get URI permission: $e")
+        }
+        return null
+    }
+
+    /** Check whether this notification is an RCN */
+    private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean =
+        sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
+
+    private fun getResumeMediaAction(action: Runnable): MediaAction {
+        return MediaAction(
+            Icon.createWithResource(context, R.drawable.ic_media_play)
+                .setTint(themeText)
+                .loadDrawable(context),
+            action,
+            context.getString(R.string.controls_media_resume),
+            context.getDrawable(R.drawable.ic_media_play_container)
+        )
+    }
+
+    private data class JobKey(val key: String, val sbn: StatusBarNotification) :
+        Pair<String, StatusBarNotification>(key, sbn)
+
+    companion object {
+        private const val TAG = "MediaDataLoader"
+        private val ART_URIS =
+            arrayOf(
+                MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+                MediaMetadata.METADATA_KEY_ART_URI,
+                MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+            )
+    }
+
+    /** Returned data from loader. */
+    data class MediaDataLoaderResult(
+        val appName: String?,
+        val appIcon: Icon?,
+        val artist: CharSequence?,
+        val song: CharSequence?,
+        val artworkIcon: Icon?,
+        val actionIcons: List<MediaAction>,
+        val actionsToShowInCompact: List<Int>,
+        val semanticActions: MediaButton?,
+        val token: MediaSession.Token?,
+        val clickIntent: PendingIntent?,
+        val device: MediaDeviceData?,
+        val playbackLocation: Int,
+        val isPlaying: Boolean?,
+        val appUid: Int,
+        val isExplicit: Boolean,
+        val resumeAction: Runnable? = null,
+        val resumeProgress: Double? = 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..4555810 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
@@ -90,7 +88,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 +133,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 +148,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 +214,7 @@
         threadFactory: ThreadFactory,
         @Main uiExecutor: Executor,
         @Main foregroundExecutor: DelayableExecutor,
-        @Main handler: Handler,
+        @Main mainDispatcher: CoroutineDispatcher,
         mediaControllerFactory: MediaControllerFactory,
         dumpManager: DumpManager,
         broadcastDispatcher: BroadcastDispatcher,
@@ -228,6 +227,7 @@
         smartspaceManager: SmartspaceManager?,
         keyguardUpdateMonitor: KeyguardUpdateMonitor,
         mediaDataRepository: MediaDataRepository,
+        mediaDataLoader: dagger.Lazy<MediaDataLoader>,
     ) : this(
         context,
         applicationScope,
@@ -237,7 +237,7 @@
         threadFactory.buildExecutorOnNewThread(TAG),
         uiExecutor,
         foregroundExecutor,
-        handler,
+        mainDispatcher,
         mediaControllerFactory,
         broadcastDispatcher,
         dumpManager,
@@ -252,6 +252,7 @@
         smartspaceManager,
         keyguardUpdateMonitor,
         mediaDataRepository,
+        mediaDataLoader,
     )
 
     private val appChangeReceiver =
@@ -271,7 +272,7 @@
         }
 
     override fun start() {
-        if (!mediaFlags.isSceneContainerEnabled()) {
+        if (!SceneContainerFlag.isEnabled) {
             return
         }
 
@@ -434,16 +435,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 +484,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 +665,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 +818,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 +1007,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 +1090,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 +1100,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 +1114,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 +1130,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 +1152,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,
@@ -1312,6 +1232,7 @@
         )
     }
 
+    @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..95ca11c 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
@@ -17,20 +17,12 @@
 package com.android.systemui.media.controls.shared
 
 import android.content.Context
-import android.graphics.drawable.AnimatedVectorDrawable
 import android.graphics.drawable.Drawable
 import com.android.systemui.Flags.mediaControlsDrawablesReuse
 import com.android.systemui.res.R
 
 object MediaControlDrawables {
 
-    // Play/Pause Button drawables.
-    private var progress: Drawable? = null
-    private var connecting: Drawable? = null
-    private var playIcon: AnimatedVectorDrawable? = null
-    private var playBackground: AnimatedVectorDrawable? = null
-    private var pauseIcon: AnimatedVectorDrawable? = null
-    private var pauseBackground: AnimatedVectorDrawable? = null
     // Prev button.
     private var prevIcon: Drawable? = null
     // Next button.
@@ -40,133 +32,54 @@
     private var antenna: Drawable? = null
     private var groupDevice: Drawable? = null
     private var homeDevices: Drawable? = null
-    // Guts drawables.
-    private var outline: Drawable? = null
-    private var solid: Drawable? = null
-
-    fun getProgress(context: Context): Drawable? {
-        return progress
-            ?: context.getDrawable(com.android.internal.R.drawable.progress_small_material).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                progress = it
-            }
-    }
-
-    fun getConnecting(context: Context): Drawable? {
-        return connecting
-            ?: context.getDrawable(R.drawable.ic_media_connecting_container).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                connecting = it
-            }
-    }
-
-    fun getPlayIcon(context: Context): AnimatedVectorDrawable? {
-        return playIcon?.let {
-            it.reset()
-            it
-        }
-            ?: (context.getDrawable(R.drawable.ic_media_play) as AnimatedVectorDrawable?).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                playIcon = it
-            }
-    }
-
-    fun getPlayBackground(context: Context): AnimatedVectorDrawable? {
-        return playBackground?.let {
-            it.reset()
-            it
-        }
-            ?: (context.getDrawable(R.drawable.ic_media_play_container) as AnimatedVectorDrawable?)
-                .also {
-                    if (!mediaControlsDrawablesReuse()) return@also
-                    playBackground = it
-                }
-    }
-
-    fun getPauseIcon(context: Context): AnimatedVectorDrawable? {
-        return pauseIcon?.let {
-            it.reset()
-            it
-        }
-            ?: (context.getDrawable(R.drawable.ic_media_pause) as AnimatedVectorDrawable?).also {
-                if (!mediaControlsDrawablesReuse()) return@also
-                pauseIcon = it
-            }
-    }
-
-    fun getPauseBackground(context: Context): AnimatedVectorDrawable? {
-        return pauseBackground?.let {
-            it.reset()
-            it
-        }
-            ?: (context.getDrawable(R.drawable.ic_media_pause_container) as AnimatedVectorDrawable?)
-                .also {
-                    if (!mediaControlsDrawablesReuse()) return@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
-            }
-    }
-
-    fun getOutline(context: Context): Drawable? {
-        return outline
-            ?: context.getDrawable(R.drawable.qs_media_outline_button).also {
-                if (!mediaControlsDrawablesReuse()) return@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
-            }
+            ?: context.getDrawable(R.drawable.ic_media_home_devices).also { homeDevices = 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/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 21c3111..d4af1b5 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
@@ -30,9 +29,8 @@
      * Check whether media control actions should be based on PlaybackState instead of notification
      */
     fun areMediaSessionActionsEnabled(packageName: String, user: UserHandle): Boolean {
-        val enabled = StatusBarManager.useMediaSessionActionsForApp(packageName, user)
         // Allow global override with flag
-        return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
+        return StatusBarManager.useMediaSessionActionsForApp(packageName, user)
     }
 
     /**
@@ -49,7 +47,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/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 9b1ca1e..6440205 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -122,8 +122,10 @@
         @Provides
         @MediaProjectionAppSelector
         @MediaProjectionAppSelectorScope
-        fun bindConfigurationController(context: Context): ConfigurationController =
-            ConfigurationControllerImpl(context)
+        fun bindConfigurationController(
+            context: Context,
+            configurationControlleFactory: ConfigurationControllerImpl.Factory
+        ): ConfigurationController = configurationControlleFactory.create(context)
 
         @Provides fun bindIconFactory(context: Context): IconFactory = IconFactory.obtain(context)
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 2dbe2aa..bf2aa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -20,7 +20,7 @@
 import android.annotation.UserIdInt
 import android.app.ActivityManager.RecentTaskInfo
 import android.content.ComponentName
-import com.android.wm.shell.util.SplitBounds
+import com.android.wm.shell.shared.split.SplitBounds
 
 data class RecentTask(
     val taskId: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 01b1be9..82e58cc 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.util.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index dd1fa76..bb4d894 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -36,10 +36,10 @@
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
 import com.android.systemui.res.R
 import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
+import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
 import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
 import java.util.Optional
 import javax.inject.Inject
 
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/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index cb0bb4a..e44069f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -563,10 +563,6 @@
     }
 
     @Override
-    public void onRecentsAnimationStateChanged(boolean running) {
-    }
-
-    @Override
     public void onNavigationModeChanged(int mode) {
         mNavigationMode = mode;
         mEdgeBackGestureHandler.onNavigationModeChanged(mode);
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/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index c706c3e..e8c90c1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -140,7 +140,6 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.rotation.RotationPolicyUtil;
@@ -166,6 +165,7 @@
 import com.android.systemui.util.ViewController;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.shared.handles.RegionSamplingHelper;
 
 import dagger.Lazy;
 
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
new file mode 100644
index 0000000..b6868c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.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.notifications.ui.viewmodel
+
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Models the UI state for the user actions for navigating to other scenes or overlays. */
+class NotificationsShadeOverlayActionsViewModel @AssistedInject constructor() :
+    UserActionsViewModel() {
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        setActions(
+            mapOf(
+                Swipe.Up to UserActionResult.HideOverlay(Overlays.NotificationsShade),
+                Back to UserActionResult.HideOverlay(Overlays.NotificationsShade),
+            )
+        )
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): NotificationsShadeOverlayActionsViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
new file mode 100644
index 0000000..5be225c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.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.systemui.notifications.ui.viewmodel
+
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/**
+ * Models UI state used to render the content of the notifications shade overlay.
+ *
+ * Different from [NotificationsShadeOverlayActionsViewModel], which only models user actions that
+ * can be performed to navigate to other scenes.
+ */
+class NotificationsShadeOverlayContentViewModel
+@AssistedInject
+constructor(
+    val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
+    val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
+    private val sceneInteractor: SceneInteractor,
+) {
+    fun onScrimClicked() {
+        sceneInteractor.hideOverlay(
+            overlay = Overlays.NotificationsShade,
+            loggingReason = "Shade scrim clicked",
+        )
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): NotificationsShadeOverlayContentViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
index 9fb09c0..a5c07bc 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeUserActionsViewModel.kt
@@ -21,29 +21,20 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeAlignment
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 
 /**
  * Models the UI state for the user actions that the user can perform to navigate to other scenes.
  */
-class NotificationsShadeSceneActionsViewModel
-@AssistedInject
-constructor(
-    private val shadeInteractor: ShadeInteractor,
-) : SceneActionsViewModel() {
+class NotificationsShadeUserActionsViewModel @AssistedInject constructor() :
+    UserActionsViewModel() {
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
         setActions(
             mapOf(
-                if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
-                    Swipe.Up
-                } else {
-                    Swipe.Down
-                } to SceneFamilies.Home,
+                Swipe.Up to SceneFamilies.Home,
                 Back to SceneFamilies.Home,
             )
         )
@@ -51,6 +42,6 @@
 
     @AssistedFactory
     interface Factory {
-        fun create(): NotificationsShadeSceneActionsViewModel
+        fun create(): NotificationsShadeUserActionsViewModel
     }
 }
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 5d81d4f..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,17 +32,24 @@
 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
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.onGloballyPositioned
 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
@@ -50,11 +57,17 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.modifiers.height
 import com.android.compose.modifiers.padding
+import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.PlatformTheme
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.dagger.MediaModule.QS_PANEL
+import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.composefragment.ui.notificationScrimClip
 import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
 import com.android.systemui.qs.flags.QSComposeFragment
 import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -65,6 +78,7 @@
 import com.android.systemui.util.LifecycleFragment
 import java.util.function.Consumer
 import javax.inject.Inject
+import javax.inject.Named
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -77,6 +91,8 @@
 @Inject
 constructor(
     private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
+    @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
+    @Named(QS_PANEL) private val qsMediaHost: MediaHost,
 ) : LifecycleFragment(), QS {
 
     private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
@@ -93,12 +109,25 @@
     private val qqsPositionOnRoot = Rect()
     private val composeViewPositionOnScreen = Rect()
 
+    // Inside object for namespacing
+    private val notificationScrimClippingParams =
+        object {
+            var isEnabled by mutableStateOf(false)
+            var leftInset by mutableStateOf(0)
+            var rightInset by mutableStateOf(0)
+            var top by mutableStateOf(0)
+            var bottom by mutableStateOf(0)
+            var radius by mutableStateOf(0)
+        }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
         QSComposeFragment.isUnexpectedlyInLegacyMode()
         viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope)
 
+        qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+        qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
         setListenerCollections()
     }
 
@@ -117,7 +146,18 @@
 
                     AnimatedVisibility(
                         visible = visible,
-                        modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+                        modifier =
+                            Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf(
+                                notificationScrimClippingParams.isEnabled
+                            ) {
+                                Modifier.notificationScrimClip(
+                                    notificationScrimClippingParams.leftInset,
+                                    notificationScrimClippingParams.top,
+                                    notificationScrimClippingParams.rightInset,
+                                    notificationScrimClippingParams.bottom,
+                                    notificationScrimClippingParams.radius,
+                                )
+                            }
                     ) {
                         AnimatedContent(targetState = qsState) {
                             when (it) {
@@ -230,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 {
@@ -271,10 +311,19 @@
         cornerRadius: Int,
         visible: Boolean,
         fullWidth: Boolean
-    ) {}
+    ) {
+        notificationScrimClippingParams.isEnabled = visible
+        notificationScrimClippingParams.top = top
+        notificationScrimClippingParams.bottom = bottom
+        // Full width means that QS will show in the entire width allocated to it (for example
+        // phone) vs. showing in a narrower column (for example, tablet portrait).
+        notificationScrimClippingParams.leftInset = if (fullWidth) 0 else leftInset
+        notificationScrimClippingParams.rightInset = if (fullWidth) 0 else rightInset
+        notificationScrimClippingParams.radius = cornerRadius
+    }
 
     override fun isFullyCollapsed(): Boolean {
-        return !viewModel.isQSVisible
+        return viewModel.qsExpansionValue <= 0f
     }
 
     override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
@@ -355,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))
         }
     }
@@ -384,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/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
new file mode 100644
index 0000000..93c6445
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
@@ -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 com.android.systemui.qs.composefragment.ui
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ClipOp
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.asAndroidPath
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+
+/**
+ * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
+ * ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)`
+ * from the QS container.
+ */
+fun Modifier.notificationScrimClip(
+    leftInset: Int,
+    top: Int,
+    rightInset: Int,
+    bottom: Int,
+    radius: Int
+): Modifier {
+    return this then NotificationScrimClipElement(leftInset, top, rightInset, bottom, radius)
+}
+
+private class NotificationScrimClipNode(
+    var leftInset: Float,
+    var top: Float,
+    var rightInset: Float,
+    var bottom: Float,
+    var radius: Float,
+) : DrawModifierNode, Modifier.Node() {
+    private val path = Path()
+
+    var invalidated = true
+
+    override fun ContentDrawScope.draw() {
+        if (invalidated) {
+            path.rewind()
+            path
+                .asAndroidPath()
+                .addRoundRect(
+                    -leftInset,
+                    top,
+                    size.width + rightInset,
+                    bottom,
+                    radius,
+                    radius,
+                    android.graphics.Path.Direction.CW
+                )
+            invalidated = false
+        }
+        clipPath(path, ClipOp.Difference) { this@draw.drawContent() }
+    }
+}
+
+private data class NotificationScrimClipElement(
+    val leftInset: Int,
+    val top: Int,
+    val rightInset: Int,
+    val bottom: Int,
+    val radius: Int,
+) : ModifierNodeElement<NotificationScrimClipNode>() {
+    override fun create(): NotificationScrimClipNode {
+        return NotificationScrimClipNode(
+            leftInset.toFloat(),
+            top.toFloat(),
+            rightInset.toFloat(),
+            bottom.toFloat(),
+            radius.toFloat(),
+        )
+    }
+
+    override fun update(node: NotificationScrimClipNode) {
+        val changed =
+            node.leftInset != leftInset.toFloat() ||
+                node.top != top.toFloat() ||
+                node.rightInset != rightInset.toFloat() ||
+                node.bottom != bottom.toFloat() ||
+                node.radius != radius.toFloat()
+        if (changed) {
+            node.leftInset = leftInset.toFloat()
+            node.top = top.toFloat()
+            node.rightInset = rightInset.toFloat()
+            node.bottom = bottom.toFloat()
+            node.radius = radius.toFloat()
+            node.invalidated = true
+        }
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "notificationScrimClip"
+        properties["leftInset"] = leftInset
+        properties["top"] = top
+        properties["rightInset"] = rightInset
+        properties["bottom"] = bottom
+        properties["radius"] = radius
+    }
+}
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 9e109e4..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
@@ -18,21 +18,27 @@
 
 import android.content.res.Resources
 import android.graphics.Rect
+import androidx.annotation.FloatRange
+import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.LifecycleCoroutineScope
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.FooterActionsController
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
+import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.util.LargeScreenUtils
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -79,11 +85,17 @@
             _qsVisible.value = value
         }
 
-    private val _qsExpansion = MutableStateFlow(0f)
+    // This can only be negative if undefined (in which case it will be -1f), else it will be
+    // in [0, 1]. In some cases, it could be set back to -1f internally to indicate that it's
+    // different to every value in [0, 1].
+    @FloatRange(from = -1.0, to = 1.0) private val _qsExpansion = MutableStateFlow(-1f)
     var qsExpansionValue: Float
         get() = _qsExpansion.value
         set(value) {
-            _qsExpansion.value = value
+            if (value < 0f) {
+                _qsExpansion.value = -1f
+            }
+            _qsExpansion.value = value.coerceIn(0f, 1f)
         }
 
     private val _panelFraction = MutableStateFlow(0f)
@@ -120,20 +132,51 @@
             _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)
 
     private val _keyguardAndExpanded = MutableStateFlow(false)
 
-    private val _statusBarState = MutableStateFlow(-1)
+    /**
+     * Tracks the current [StatusBarState]. It will switch early if the upcoming state is
+     * [StatusBarState.KEYGUARD]
+     */
+    @get:VisibleForTesting
+    val statusBarState =
+        conflatedCallbackFlow {
+                val callback =
+                    object : StatusBarStateController.StateListener {
+                        override fun onStateChanged(newState: Int) {
+                            trySend(newState)
+                        }
+
+                        override fun onUpcomingStateChanged(upcomingState: Int) {
+                            if (upcomingState == StatusBarState.KEYGUARD) {
+                                trySend(upcomingState)
+                            }
+                        }
+                    }
+                sysuiStatusBarStateController.addCallback(callback)
+
+                awaitClose { sysuiStatusBarStateController.removeCallback(callback) }
+            }
+            .stateIn(
+                lifecycleScope,
+                SharingStarted.WhileSubscribed(),
+                sysuiStatusBarStateController.state,
+            )
 
     private val _viewHeight = MutableStateFlow(0)
 
@@ -179,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/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index d948dfd..c75b601 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.runtime.Composable
@@ -24,7 +23,6 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.dimensionResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
@@ -33,7 +31,6 @@
 import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.res.R
 import javax.inject.Inject
 
 @SysUISingleton
@@ -64,7 +61,7 @@
                 Tile(
                     tile = sizedTiles[index].tile,
                     iconOnly = iconTilesViewModel.isIconTile(sizedTiles[index].tile.spec),
-                    modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+                    modifier = Modifier
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index a9027ff..eeb55ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -16,18 +16,15 @@
 
 package com.android.systemui.qs.panels.ui.compose
 
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.dimensionResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
-import com.android.systemui.res.R
 
 @Composable
 fun QuickQuickSettings(
@@ -54,11 +51,7 @@
             key = { index -> sizedTiles[index].tile.spec.spec },
             span = { index -> GridItemSpan(sizedTiles[index].width) }
         ) { index ->
-            Tile(
-                tile = tiles[index],
-                iconOnly = sizedTiles[index].isIcon,
-                modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
-            )
+            Tile(tile = tiles[index], iconOnly = sizedTiles[index].isIcon, modifier = Modifier)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 6eacb2e..24af09d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -24,6 +24,7 @@
 import android.text.TextUtils
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
@@ -43,7 +44,6 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -81,6 +81,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.LocalContext
@@ -132,25 +133,31 @@
     val uiState = remember(state) { state.toUiState() }
     val colors = TileDefaults.getColorForState(uiState)
 
+    // TODO(b/361789146): Draw the shapes instead of clipping
+    val tileShape = TileDefaults.animateTileShape(uiState.state)
+
     TileContainer(
         colors = colors,
         showLabels = showLabels,
         label = uiState.label,
         iconOnly = iconOnly,
+        shape = tileShape,
         clickEnabled = true,
         onClick = tile::onClick,
         onLongClick = tile::onLongClick,
-        modifier = modifier,
+        modifier = modifier.height(tileHeight()),
     ) {
         val icon = getTileIcon(icon = uiState.icon)
         if (iconOnly) {
             TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center))
         } else {
+            val iconShape = TileDefaults.animateIconShape(uiState.state)
             LargeTileContent(
                 label = uiState.label,
                 secondaryLabel = uiState.secondaryLabel,
                 icon = icon,
                 colors = colors,
+                iconShape = iconShape,
                 toggleClickSupported = state.handlesSecondaryClick,
                 onClick = {
                     if (state.handlesSecondaryClick) {
@@ -169,6 +176,7 @@
     showLabels: Boolean,
     label: String,
     iconOnly: Boolean,
+    shape: Shape,
     clickEnabled: Boolean = false,
     onClick: (Expandable) -> Unit = {},
     onLongClick: (Expandable) -> Unit = {},
@@ -189,10 +197,8 @@
             }
         Expandable(
             color = backgroundColor,
-            shape = TileDefaults.TileShape,
-            modifier =
-                Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
-                    .clip(TileDefaults.TileShape)
+            shape = shape,
+            modifier = Modifier.height(tileHeight()).clip(shape)
         ) {
             Box(
                 modifier =
@@ -227,6 +233,7 @@
     secondaryLabel: String?,
     icon: Icon,
     colors: TileColors,
+    iconShape: Shape,
     toggleClickSupported: Boolean = false,
     onClick: () -> Unit = {},
     onLongClick: () -> Unit = {},
@@ -238,8 +245,8 @@
         // Icon
         Box(
             modifier =
-                Modifier.fillMaxHeight().aspectRatio(1f).thenIf(toggleClickSupported) {
-                    Modifier.clip(TileDefaults.TileShape)
+                Modifier.size(TileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
+                    Modifier.clip(iconShape)
                         .background(colors.iconBackground, { 1f })
                         .combinedClickable(onClick = onClick, onLongClick = onLongClick)
                 }
@@ -391,7 +398,7 @@
         horizontalArrangement = tileHorizontalArrangement(),
         modifier =
             Modifier.fillMaxHeight()
-                .border(1.dp, LocalContentColor.current, shape = TileDefaults.TileShape)
+                .border(1.dp, LocalContentColor.current, shape = CircleShape)
                 .padding(10.dp)
     ) {
         Icon(imageVector = Icons.Default.Clear, contentDescription = null)
@@ -533,7 +540,7 @@
                         Modifier.background(
                                 color = MaterialTheme.colorScheme.secondary,
                                 alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA },
-                                shape = TileDefaults.TileShape
+                                shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius)
                             )
                             .animateItem()
                     )
@@ -619,6 +626,7 @@
         showLabels = showLabels,
         label = label,
         iconOnly = iconOnly,
+        shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
         modifier = modifier,
     ) {
         if (iconOnly) {
@@ -633,6 +641,7 @@
                 secondaryLabel = tileViewModel.appName?.load(),
                 icon = tileViewModel.icon,
                 colors = colors,
+                iconShape = RoundedCornerShape(TileDefaults.InactiveCornerRadius),
             )
         }
     }
@@ -663,7 +672,7 @@
     animateToEnd: Boolean = false,
     modifier: Modifier = Modifier,
 ) {
-    val iconModifier = modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
+    val iconModifier = modifier.size(TileDefaults.IconSize)
     val context = LocalContext.current
     val loadedDrawable =
         remember(icon, context) {
@@ -700,17 +709,12 @@
     }
 }
 
-@Composable
 private fun Modifier.tilePadding(): Modifier {
-    return padding(dimensionResource(id = R.dimen.qs_label_container_margin))
+    return padding(TileDefaults.TilePadding)
 }
 
-@Composable
 private fun tileHorizontalArrangement(): Arrangement.Horizontal {
-    return spacedBy(
-        space = dimensionResource(id = R.dimen.qs_label_container_margin),
-        alignment = Alignment.Start
-    )
+    return spacedBy(space = TileDefaults.TileArrangementPadding, alignment = Alignment.Start)
 }
 
 @Composable
@@ -718,7 +722,7 @@
     return if (iconWithLabel) {
         TileDefaults.IconTileWithLabelHeight
     } else {
-        dimensionResource(id = R.dimen.qs_tile_height)
+        TileDefaults.TileHeight
     }
 }
 
@@ -736,7 +740,17 @@
 }
 
 private object TileDefaults {
-    val TileShape = CircleShape
+    val InactiveCornerRadius = 50.dp
+    val ActiveIconCornerRadius = 16.dp
+    val ActiveTileCornerRadius = 24.dp
+
+    val ToggleTargetSize = 56.dp
+    val IconSize = 24.dp
+
+    val TilePadding = 8.dp
+    val TileArrangementPadding = 6.dp
+
+    val TileHeight = 72.dp
     val IconTileWithLabelHeight = 140.dp
 
     /** An active tile without dual target uses the active color as background */
@@ -795,6 +809,39 @@
             else -> unavailableTileColors()
         }
     }
+
+    @Composable
+    fun animateIconShape(state: Int): Shape {
+        return animateShape(
+            state = state,
+            activeCornerRadius = ActiveIconCornerRadius,
+            label = "QSTileCornerRadius",
+        )
+    }
+
+    @Composable
+    fun animateTileShape(state: Int): Shape {
+        return animateShape(
+            state = state,
+            activeCornerRadius = ActiveTileCornerRadius,
+            label = "QSTileIconCornerRadius",
+        )
+    }
+
+    @Composable
+    fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape {
+        val animatedCornerRadius by
+            animateDpAsState(
+                targetValue =
+                    if (state == STATE_ACTIVE) {
+                        activeCornerRadius
+                    } else {
+                        InactiveCornerRadius
+                    },
+                label = label
+            )
+        return RoundedCornerShape(animatedCornerRadius)
+    }
 }
 
 private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
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/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 6d63d26..7d23fbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -21,6 +21,8 @@
 import android.os.Handler
 import android.os.Looper
 import android.service.quicksettings.Tile
+import androidx.annotation.DrawableRes
+import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.coroutineScope
 import androidx.lifecycle.repeatOnLifecycle
@@ -62,7 +64,7 @@
     activityStarter: ActivityStarter,
     qsLogger: QSLogger,
     qsTileConfigProvider: QSTileConfigProvider,
-    dataInteractor: ModesTileDataInteractor,
+    private val dataInteractor: ModesTileDataInteractor,
     private val tileMapper: ModesTileMapper,
     private val userActionInteractor: ModesTileUserActionInteractor,
 ) :
@@ -98,7 +100,7 @@
     override fun newTileState(): QSTile.State {
         return QSTile.State().apply {
             label = mContext.getString(R.string.quick_settings_modes_label)
-            icon = ResourceIcon.get(R.drawable.qs_dnd_icon_off)
+            icon = ResourceIcon.get(ICON_RES_ID)
             state = Tile.STATE_INACTIVE
         }
     }
@@ -109,23 +111,26 @@
 
     override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
 
-    override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
-        if (arg is ModesTileModel) {
-            tileState = tileMapper.map(config, arg)
+    @VisibleForTesting
+    public override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
+        // This runBlocking() will block @Background. Due to caches, it's expected to be fast.
+        val model =
+            if (arg is ModesTileModel) arg else runBlocking { dataInteractor.getCurrentTileModel() }
 
-            state?.apply {
-                this.state = tileState.activationState.legacyState
-                val tileStateIcon = tileState.icon()
-                icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(R.drawable.qs_dnd_icon_off)
-                label = tileLabel
-                secondaryLabel = tileState.secondaryLabel
-                contentDescription = tileState.contentDescription
-                expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
-            }
+        tileState = tileMapper.map(config, model)
+        state?.apply {
+            this.state = tileState.activationState.legacyState
+            val tileStateIcon = tileState.icon()
+            icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
+            label = tileLabel
+            secondaryLabel = tileState.secondaryLabel
+            contentDescription = tileState.contentDescription
+            expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
         }
     }
 
     companion object {
         const val TILE_SPEC = "dnd"
+        @DrawableRes val ICON_RES_ID = com.android.internal.R.drawable.ic_zen_priority_modes
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 71f8639..89b9eee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -306,6 +306,8 @@
                 mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE);
         mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(context));
         mWifiRecyclerView.setAdapter(mAdapter);
+
+        updateDialogUI(getWifiNetworkContent());
     }
 
     @Override
@@ -315,6 +317,7 @@
         }
 
         mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
+
         mInternetDialogController.onStart(this, mCanConfigWifi);
         if (!mCanConfigWifi) {
             hideWifiViews();
@@ -402,10 +405,12 @@
         internetContent.mShouldUpdateMobileNetwork = shouldUpdateMobileNetwork;
         internetContent.mInternetDialogTitleString = getDialogTitleText();
         internetContent.mInternetDialogSubTitle = getSubtitleText();
-        internetContent.mActiveNetworkIsCellular =
-                mInternetDialogController.activeNetworkIsCellular();
-        internetContent.mIsCarrierNetworkActive =
-                mInternetDialogController.isCarrierNetworkActive();
+        if (shouldUpdateMobileNetwork) {
+            internetContent.mActiveNetworkIsCellular =
+                    mInternetDialogController.activeNetworkIsCellular();
+            internetContent.mIsCarrierNetworkActive =
+                    mInternetDialogController.isCarrierNetworkActive();
+        }
         internetContent.mIsAirplaneModeEnabled = mInternetDialogController.isAirplaneModeEnabled();
         internetContent.mHasEthernet = mInternetDialogController.hasEthernet();
         internetContent.mIsWifiEnabled = mInternetDialogController.isWifiEnabled();
@@ -416,6 +421,15 @@
         return internetContent;
     }
 
+    private InternetContent getWifiNetworkContent() {
+        InternetContent internetContent = new InternetContent();
+        internetContent.mInternetDialogTitleString = getDialogTitleText();
+        internetContent.mInternetDialogSubTitle = getSubtitleText();
+        internetContent.mIsWifiEnabled = mInternetDialogController.isWifiEnabled();
+        internetContent.mIsDeviceLocked = mInternetDialogController.isDeviceLocked();
+        return internetContent;
+    }
+
     private void setOnClickListener(SystemUIDialog dialog) {
         mMobileNetworkLayout.setOnClickListener(v -> {
             int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
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..c2d112e 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
@@ -20,11 +20,13 @@
 import android.content.Context
 import android.os.UserHandle
 import com.android.app.tracing.coroutines.flow.map
+import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -52,18 +54,36 @@
      */
     fun tileData() =
         zenModeInteractor.activeModes
-            .map { modes ->
-                ModesTileModel(
-                    isActivated = modes.isNotEmpty(),
-                    icon =
-                        if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons())
-                            zenModeInteractor.getActiveModeIcon(context, modes)
-                        else null,
-                    activeModes = modes.map { it.name }
-                )
-            }
+            .map { activeModes -> buildTileData(activeModes) }
             .flowOn(bgDispatcher)
             .distinctUntilChanged()
 
+    suspend fun getCurrentTileModel() = buildTileData(zenModeInteractor.getActiveModes())
+
+    private fun buildTileData(activeModes: ActiveZenModes): ModesTileModel {
+        val modesIconResId = com.android.internal.R.drawable.ic_zen_priority_modes
+
+        if (usesModeIcons()) {
+            val mainModeDrawable = activeModes.mainMode?.icon?.drawable
+            val iconResId = if (mainModeDrawable == null) modesIconResId else null
+
+            return ModesTileModel(
+                isActivated = activeModes.isAnyActive(),
+                icon = (mainModeDrawable ?: context.getDrawable(modesIconResId)!!).asIcon(),
+                iconResId = iconResId,
+                activeModes = activeModes.modeNames
+            )
+        } else {
+            return ModesTileModel(
+                isActivated = activeModes.isAnyActive(),
+                icon = context.getDrawable(modesIconResId)!!.asIcon(),
+                iconResId = modesIconResId,
+                activeModes = activeModes.modeNames
+            )
+        }
+    }
+
     override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi())
+
+    private fun usesModeIcons() = Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
index 904ff3a..db48123 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
@@ -21,5 +21,12 @@
 data class ModesTileModel(
     val isActivated: Boolean,
     val activeModes: List<String>,
-    val icon: Icon? = null
+    val icon: Icon.Loaded,
+
+    /**
+     * Resource id corresponding to [icon]. Will only be present if it's know to correspond to a
+     * resource with a known id in SystemUI (such as resources from `android.R`,
+     * `com.android.internal.R`, or `com.android.systemui.res` itself).
+     */
+    val iconResId: Int? = null
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 0e127e3..7f571b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -16,11 +16,9 @@
 
 package com.android.systemui.qs.tiles.impl.modes.ui
 
-import android.app.Flags
 import android.content.res.Resources
 import android.icu.text.MessageFormat
 import android.widget.Button
-import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
@@ -38,14 +36,10 @@
 ) : QSTileDataToStateMapper<ModesTileModel> {
     override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() && data.icon != null) {
-                icon = { data.icon }
-            } else {
-                val defaultIconRes =
-                    if (data.isActivated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off
-                iconRes = defaultIconRes
-                icon = { resources.getDrawable(defaultIconRes, theme).asIcon() }
+            if (!android.app.Flags.modesUiIcons()) {
+                iconRes = data.iconResId
             }
+            icon = { data.icon }
             activationState =
                 if (data.isActivated) {
                     QSTileState.ActivationState.ACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index dfcf216..ac6ebe7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -147,17 +147,17 @@
     sealed interface State {
 
         val isVisible: Boolean
-        val expansion: Float
+        val expansion: () -> Float
         val squishiness: () -> Float
 
         data object CLOSED : State {
             override val isVisible = false
-            override val expansion = 0f
+            override val expansion = { 0f }
             override val squishiness = { 1f }
         }
 
         /** State for expanding between QQS and QS */
-        data class Expanding(override val expansion: Float) : State {
+        class Expanding(override val expansion: () -> Float) : State {
             override val isVisible = true
             override val squishiness = { 1f }
         }
@@ -170,7 +170,7 @@
          */
         class UnsquishingQQS(override val squishiness: () -> Float) : State {
             override val isVisible = true
-            override val expansion = 0f
+            override val expansion = { 0f }
         }
 
         /**
@@ -181,16 +181,16 @@
          */
         class UnsquishingQS(override val squishiness: () -> Float) : State {
             override val isVisible = true
-            override val expansion = 1f
+            override val expansion = { 1f }
         }
 
         companion object {
             // These are special cases of the expansion.
-            val QQS = Expanding(0f)
-            val QS = Expanding(1f)
+            val QQS = Expanding { 0f }
+            val QS = Expanding { 1f }
 
             /** Collapsing from QS to QQS. [progress] is 0f in QS and 1f in QQS. */
-            fun Collapsing(progress: Float) = Expanding(1f - progress)
+            fun Collapsing(progress: () -> Float) = Expanding { 1f - progress() }
         }
     }
 }
@@ -418,14 +418,14 @@
 
     private fun QSImpl.applyState(state: QSSceneAdapter.State) {
         setQsVisible(state.isVisible)
-        setExpanded(state.isVisible && state.expansion > 0f)
+        setExpanded(state.isVisible && state.expansion() > 0f)
         setListening(state.isVisible)
     }
 
     override fun applyLatestExpansionAndSquishiness() {
         val qsImpl = _qsImpl.value
         val state = state.value
-        qsImpl?.setQsExpansion(state.expansion, 1f, 0f, state.squishiness())
+        qsImpl?.setQsExpansion(state.expansion(), 1f, 0f, state.squishiness())
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
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..f77386d 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,22 +17,29 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import androidx.lifecycle.LifecycleOwner
-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
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
 
 /**
  * Models UI state needed for rendering the content of the quick settings scene.
  *
- * Different from [QuickSettingsSceneActionsViewModel] that models the UI state needed to figure out
+ * Different from [QuickSettingsUserActionsViewModel] that models the UI state needed to figure out
  * which user actions can trigger navigation to other scenes.
  */
 class QuickSettingsSceneContentViewModel
@@ -44,7 +51,9 @@
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
     val mediaCarouselInteractor: MediaCarouselInteractor,
-) : SysUiViewModel() {
+    private val shadeInteractor: ShadeInteractor,
+    private val sceneInteractor: SceneInteractor,
+) : ExclusiveActivatable() {
 
     val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
 
@@ -57,6 +66,19 @@
         return footerActionsViewModelFactory.create(lifecycleOwner)
     }
 
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch {
+                shadeInteractor.shadeMode.collect { shadeMode ->
+                    if (shadeMode == ShadeMode.Split) {
+                        sceneInteractor.snapToScene(Scenes.Shade, "Unfold while on QS")
+                    }
+                }
+            }
+            awaitCancellation()
+        }
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): QuickSettingsSceneContentViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
new file mode 100644
index 0000000..61c4c8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.viewmodel
+
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/** Models the UI state for the user actions for navigating to other scenes or overlays. */
+class QuickSettingsShadeOverlayActionsViewModel @AssistedInject constructor() :
+    UserActionsViewModel() {
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        setActions(
+            buildMap {
+                put(Swipe.Up, UserActionResult.HideOverlay(Overlays.QuickSettingsShade))
+                put(Back, UserActionResult.HideOverlay(Overlays.QuickSettingsShade))
+            }
+        )
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): QuickSettingsShadeOverlayActionsViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
new file mode 100644
index 0000000..3b97d82
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.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.systemui.qs.ui.viewmodel
+
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/**
+ * Models UI state used to render the content of the quick settings shade overlay.
+ *
+ * Different from [QuickSettingsShadeOverlayActionsViewModel], which only models user actions that
+ * can be performed to navigate to other scenes.
+ */
+class QuickSettingsShadeOverlayContentViewModel
+@AssistedInject
+constructor(
+    val sceneInteractor: SceneInteractor,
+    val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
+    val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
+) {
+    fun onScrimClicked() {
+        sceneInteractor.hideOverlay(
+            overlay = Overlays.QuickSettingsShade,
+            loggingReason = "Shade scrim clicked",
+        )
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): QuickSettingsShadeOverlayContentViewModel
+    }
+}
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..d01b33b 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
@@ -14,28 +14,22 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 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
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 /**
  * Models UI state used to render the content of the quick settings shade scene.
  *
- * Different from [QuickSettingsShadeSceneActionsViewModel], which only models user actions that can
+ * Different from [QuickSettingsShadeUserActionsViewModel], which only models user actions that can
  * be performed to navigate to other scenes.
  */
 class QuickSettingsShadeSceneContentViewModel
 @AssistedInject
 constructor(
-    val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
     val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) : SysUiViewModel() {
+) {
 
     @AssistedFactory
     interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModel.kt
similarity index 66%
rename from packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModel.kt
index d2967b8..d3dc302 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModel.kt
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.qs.ui.viewmodel
 
 import com.android.compose.animation.scene.Back
@@ -23,13 +21,9 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeAlignment
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
 
 /**
@@ -38,35 +32,27 @@
  * Different from the [QuickSettingsShadeSceneContentViewModel] which models the _content_ of the
  * scene.
  */
-class QuickSettingsShadeSceneActionsViewModel
+class QuickSettingsShadeUserActionsViewModel
 @AssistedInject
 constructor(
-    private val shadeInteractor: ShadeInteractor,
     val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
         quickSettingsContainerViewModel.editModeViewModel.isEditing
             .map { editing ->
                 buildMap {
-                    put(
-                        if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
-                            Swipe.Up
-                        } else {
-                            Swipe.Down
-                        },
-                        UserActionResult(SceneFamilies.Home)
-                    )
+                    put(Swipe.Up, UserActionResult(SceneFamilies.Home))
                     if (!editing) {
                         put(Back, UserActionResult(SceneFamilies.Home))
                     }
                 }
             }
-            .collectLatest { actions -> setActions(actions) }
+            .collect { actions -> setActions(actions) }
     }
 
     @AssistedFactory
     interface Factory {
-        fun create(): QuickSettingsShadeSceneActionsViewModel
+        fun create(): QuickSettingsShadeUserActionsViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt
index af55f5a..54e5cac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsUserActionsViewModel.kt
@@ -27,11 +27,10 @@
 import com.android.systemui.scene.domain.interactor.SceneBackInteractor
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
 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
@@ -43,12 +42,12 @@
  * Different from [QuickSettingsSceneContentViewModel] that models UI state needed for rendering the
  * content of the quick settings scene.
  */
-class QuickSettingsSceneActionsViewModel
+class QuickSettingsUserActionsViewModel
 @AssistedInject
 constructor(
     private val qsSceneAdapter: QSSceneAdapter,
     sceneBackInteractor: SceneBackInteractor,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
 
     private val backScene: Flow<SceneKey> =
         sceneBackInteractor.backScene
@@ -78,11 +77,11 @@
                     }
                 }
             }
-            .collectLatest { actions -> setActions(actions) }
+            .collect { actions -> setActions(actions) }
     }
 
     @AssistedFactory
     interface Factory {
-        fun create(): QuickSettingsSceneActionsViewModel
+        fun create(): QuickSettingsUserActionsViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ecf816b..000781a 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());
     }
 
@@ -896,11 +894,21 @@
             return;
         }
         mHandler.removeCallbacks(mConnectionRunnable);
+
+        // Avoid creating TouchInteractionService because the System user in HSUM mode does not
+        // interact with UI elements
+        UserHandle currentUser = UserHandle.of(mUserTracker.getUserId());
+        if (UserManager.isHeadlessSystemUserMode() && currentUser.isSystem()) {
+            Log.w(TAG_OPS,
+                    "Skipping connection to TouchInteractionService for the System user in HSUM "
+                            + "mode.");
+            return;
+        }
         try {
             mBound = mContext.bindServiceAsUser(mQuickStepIntent,
                     mOverviewServiceConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                    UserHandle.of(mUserTracker.getUserId()));
+                    currentUser);
         } catch (SecurityException e) {
             Log.e(TAG_OPS, "Unable to bind because of security error", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt
index 14dfcc5..b0eaf9f 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/CustomTraceState.kt
@@ -21,6 +21,10 @@
 import com.android.traceur.PresetTraceConfigs.getDefaultConfig
 import com.android.traceur.TraceConfig
 
+/**
+ * This class encapsulates the values that go into a customized record issue trace config, part of
+ * the RecordIssueTile feature. This class stores the last configuration chosen by power users.
+ */
 class CustomTraceState(private val prefs: SharedPreferences) {
 
     private var enabledTags: Set<String>?
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 863a899..3d6d00e 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -24,6 +24,7 @@
 import android.net.Uri
 import android.os.Handler
 import android.os.UserHandle
+import android.provider.Settings
 import android.util.Log
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.DialogTransitionAnimator
@@ -90,7 +91,16 @@
                 // ViewCapture needs to save it's data before it is disabled, or else the data will
                 // be lost. This is expected to change in the near future, and when that happens
                 // this line should be removed.
-                bgExecutor.execute { traceurMessageSender.stopTracing() }
+                bgExecutor.execute {
+                    if (issueRecordingState.traceConfig.longTrace) {
+                        Settings.Global.putInt(
+                            contentResolver,
+                            NOTIFY_SESSION_ENDED_SETTING,
+                            DISABLED
+                        )
+                    }
+                    traceurMessageSender.stopTracing()
+                }
                 issueRecordingState.isRecording = false
             }
             ACTION_SHARE -> {
@@ -125,6 +135,8 @@
     companion object {
         private const val TAG = "IssueRecordingService"
         private const val CHANNEL_ID = "issue_record"
+        private const val NOTIFY_SESSION_ENDED_SETTING = "should_notify_trace_session_ended"
+        private const val DISABLED = 0
 
         /**
          * Get an intent to stop the issue recording service.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/EmptySceneModule.kt b/packages/SystemUI/src/com/android/systemui/scene/EmptySceneModule.kt
index efb9375..7a57fba 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/EmptySceneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/EmptySceneModule.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.scene
 
-import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.ui.composable.Overlay
+import com.android.systemui.scene.ui.composable.Scene
 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/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 6e89973..00944b8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.domain.startable.ScrimStartable
 import com.android.systemui.scene.domain.startable.StatusBarStartable
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.shared.flag.DualShade
@@ -42,8 +43,10 @@
         [
             EmptySceneModule::class,
             GoneSceneModule::class,
+            NotificationsShadeOverlayModule::class,
             NotificationsShadeSceneModule::class,
             NotificationsShadeSessionModule::class,
+            QuickSettingsShadeOverlayModule::class,
             QuickSettingsSceneModule::class,
             ShadeSceneModule::class,
             SceneDomainModule::class,
@@ -99,6 +102,11 @@
                         Scenes.Shade.takeUnless { DualShade.isEnabled },
                     ),
                 initialSceneKey = Scenes.Gone,
+                overlayKeys =
+                    listOfNotNull(
+                        Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
+                        Overlays.QuickSettingsShade.takeIf { DualShade.isEnabled },
+                    ),
                 navigationDistances =
                     mapOf(
                             Scenes.Gone to 0,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 7d63b4c..4061ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.scene
 
 import com.android.systemui.CoreStartable
-import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule
 import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
 import com.android.systemui.scene.domain.SceneDomainModule
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
@@ -28,6 +27,7 @@
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.domain.startable.ScrimStartable
 import com.android.systemui.scene.domain.startable.StatusBarStartable
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.shared.flag.DualShade
@@ -43,13 +43,14 @@
         [
             BouncerSceneModule::class,
             CommunalSceneModule::class,
-            ComposeBouncerFlagsModule::class,
             EmptySceneModule::class,
             GoneSceneModule::class,
             LockscreenSceneModule::class,
             QuickSettingsSceneModule::class,
             ShadeSceneModule::class,
+            QuickSettingsShadeOverlayModule::class,
             QuickSettingsShadeSceneModule::class,
+            NotificationsShadeOverlayModule::class,
             NotificationsShadeSceneModule::class,
             NotificationsShadeSessionModule::class,
             SceneDomainModule::class,
@@ -108,6 +109,11 @@
                         Scenes.Shade.takeUnless { DualShade.isEnabled },
                     ),
                 initialSceneKey = Scenes.Lockscreen,
+                overlayKeys =
+                    listOfNotNull(
+                        Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
+                        Overlays.QuickSettingsShade.takeIf { DualShade.isEnabled },
+                    ),
                 navigationDistances =
                     mapOf(
                             Scenes.Gone to 0,
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/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
index c176cca..2d40845 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
@@ -58,29 +58,20 @@
 
     fun onSceneChange(from: SceneKey, to: SceneKey) {
         check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" }
-        when (stackOperation(from, to)) {
-            Clear -> {
-                _backStack.value = sceneStackOf()
-            }
-            Push -> {
-                _backStack.update { s -> s.push(from) }
-            }
-            Pop -> {
-                _backStack.update { s ->
-                    checkNotNull(s.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
-                        .also {
-                            val popped = s.peek()
-                            check(popped == to) {
-                                "Expected to pop ${to.debugName} but instead popped ${popped?.debugName}"
-                            }
-                        }
-                }
+
+        _backStack.update { stack ->
+            when (stackOperation(from, to, stack)) {
+                null -> stack
+                Clear -> sceneStackOf()
+                Push -> stack.push(from)
+                Pop ->
+                    checkNotNull(stack.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
             }
         }
         logger.logSceneBackStack(backStack.value.asIterable())
     }
 
-    private fun stackOperation(from: SceneKey, to: SceneKey): StackOperation {
+    private fun stackOperation(from: SceneKey, to: SceneKey, stack: SceneStack): StackOperation? {
         val fromDistance =
             checkNotNull(sceneContainerConfig.navigationDistances[from]) {
                 "No distance mapping for scene \"${from.debugName}\"!"
@@ -93,6 +84,7 @@
         return when {
             toDistance == 0 -> Clear
             toDistance > fromDistance -> Push
+            stack.peek() != to -> null
             toDistance < fromDistance -> Pop
             else ->
                 error(
@@ -103,7 +95,10 @@
     }
 
     private sealed interface StackOperation
+
     private data object Clear : StackOperation
+
     private data object Push : StackOperation
+
     private data object Pop : StackOperation
 }
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 2d510e1..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,8 +118,11 @@
         get() =
             when (this) {
                 is ObservableTransitionState.Idle -> currentScene.canBeOccluded
-                is ObservableTransitionState.Transition ->
+                is ObservableTransitionState.Transition.ChangeScene ->
                     fromScene.canBeOccluded && toScene.canBeOccluded
+                is ObservableTransitionState.Transition.ReplaceOverlay,
+                is ObservableTransitionState.Transition.ShowOrHideOverlay ->
+                    TODO("b/359173565: Handle overlay transitions")
             }
 
     /**
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 5885193..0d24adc 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
@@ -28,8 +30,6 @@
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.util.kotlin.getValue
-import com.android.systemui.util.kotlin.pairwiseBy
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -53,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
@@ -78,25 +79,28 @@
     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
      * one is considered the committed/current scene.
      */
-    val currentScene: StateFlow<SceneKey> =
-        repository.currentScene
-            .pairwiseBy(initialValue = repository.currentScene.value) { from, to ->
-                logger.logSceneChangeCommitted(
-                    from = from,
-                    to = to,
-                )
-                to
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = repository.currentScene.value,
-            )
+    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.
@@ -124,7 +128,15 @@
      */
     val transitioningTo: StateFlow<SceneKey?> =
         transitionState
-            .map { state -> (state as? ObservableTransitionState.Transition)?.toScene }
+            .map { state ->
+                when (state) {
+                    is ObservableTransitionState.Idle -> null
+                    is ObservableTransitionState.Transition.ChangeScene -> state.toScene
+                    is ObservableTransitionState.Transition.ShowOrHideOverlay,
+                    is ObservableTransitionState.Transition.ReplaceOverlay ->
+                        TODO("b/359173565: Handle overlay transitions")
+                }
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
@@ -155,11 +167,11 @@
     val isVisible: StateFlow<Boolean> =
         combine(
                 repository.isVisible,
-                repository.isRemoteUserInteractionOngoing,
+                repository.isRemoteUserInputOngoing,
             ) { isVisible, isRemoteUserInteractionOngoing ->
                 isVisibleInternal(
                     raw = isVisible,
-                    isRemoteUserInteractionOngoing = isRemoteUserInteractionOngoing,
+                    isRemoteUserInputOngoing = isRemoteUserInteractionOngoing,
                 )
             }
             .stateIn(
@@ -169,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].
@@ -185,8 +202,8 @@
                 }
                 is ObservableTransitionState.Transition -> {
                     when {
-                        transition.toScene == scene -> transition.progress
-                        transition.fromScene == scene -> transition.progress.map { 1f - it }
+                        transition.toContent == scene -> transition.progress
+                        transition.fromContent == scene -> transition.progress.map { 1f - it }
                         else -> flowOf(0f)
                     }
                 }
@@ -194,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)
     }
@@ -234,14 +241,15 @@
             return
         }
 
-        logger.logSceneChangeRequested(
+        onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
+
+        logger.logSceneChanged(
             from = currentSceneKey,
             to = resolvedScene,
             reason = loggingReason,
             isInstant = false,
         )
 
-        onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(resolvedScene, sceneState) }
         repository.changeScene(resolvedScene, transitionKey)
     }
 
@@ -274,7 +282,7 @@
             return
         }
 
-        logger.logSceneChangeRequested(
+        logger.logSceneChanged(
             from = currentSceneKey,
             to = resolvedScene,
             reason = loggingReason,
@@ -285,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
@@ -307,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
@@ -317,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
     }
 
     /**
@@ -357,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
     }
 
     /**
@@ -378,12 +496,12 @@
         to: SceneKey,
         loggingReason: String,
     ): Boolean {
-        if (!repository.allSceneKeys().contains(to)) {
+        if (to !in repository.allContentKeys) {
             return false
         }
 
         val inMidTransitionFromGone =
-            (transitionState.value as? ObservableTransitionState.Transition)?.fromScene ==
+            (transitionState.value as? ObservableTransitionState.Transition)?.fromContent ==
                 Scenes.Gone
         val isChangeAllowed =
             to != Scenes.Gone ||
@@ -399,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/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index 9c2b992..e51a8bc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -81,14 +81,14 @@
                                 state.currentScene == Scenes.QuickSettingsShade ||
                                 state.currentScene == Scenes.Lockscreen
                         is ObservableTransitionState.Transition ->
-                            state.toScene == Scenes.Shade ||
-                                state.toScene == Scenes.NotificationsShade ||
-                                state.toScene == Scenes.QuickSettingsShade ||
-                                state.toScene == Scenes.Lockscreen ||
-                                state.fromScene == Scenes.Shade ||
-                                state.fromScene == Scenes.NotificationsShade ||
-                                state.fromScene == Scenes.QuickSettingsShade ||
-                                state.fromScene == Scenes.Lockscreen
+                            state.toContent == Scenes.Shade ||
+                                state.toContent == Scenes.NotificationsShade ||
+                                state.toContent == Scenes.QuickSettingsShade ||
+                                state.toContent == Scenes.Lockscreen ||
+                                state.fromContent == Scenes.Shade ||
+                                state.fromContent == Scenes.NotificationsShade ||
+                                state.fromContent == Scenes.QuickSettingsShade ||
+                                state.fromContent == Scenes.Lockscreen
                     }
                 }
                 .distinctUntilChanged()
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..e251c9e 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
@@ -36,6 +36,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.DisplayId
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
@@ -62,6 +63,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
@@ -78,6 +80,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
@@ -107,6 +110,7 @@
     @Application private val applicationScope: CoroutineScope,
     private val sceneInteractor: SceneInteractor,
     private val deviceEntryInteractor: DeviceEntryInteractor,
+    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
     private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
     private val bouncerInteractor: BouncerInteractor,
     private val keyguardInteractor: KeyguardInteractor,
@@ -134,6 +138,7 @@
     private val dismissCallbackRegistry: DismissCallbackRegistry,
     private val statusBarStateController: SysuiStatusBarStateController,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val vibratorHelper: VibratorHelper,
 ) : CoreStartable {
     private val centralSurfaces: CentralSurfaces?
         get() = centralSurfacesOptLazy.get().getOrNull()
@@ -148,6 +153,7 @@
             respondToFalsingDetections()
             hydrateInteractionState()
             handleBouncerOverscroll()
+            handleDeviceEntryHapticsWhileDeviceLocked()
             hydrateWindowController()
             hydrateBackStack()
             resetShadeSessions()
@@ -189,7 +195,7 @@
                             // current scene
                             when (state) {
                                 is ObservableTransitionState.Idle -> state.currentScene
-                                is ObservableTransitionState.Transition -> state.fromScene
+                                is ObservableTransitionState.Transition -> state.fromContent
                             }.let { it == Scenes.Shade || it == Scenes.QuickSettings }
                         }
                         .distinctUntilChanged()
@@ -220,7 +226,7 @@
                                             }
                                         }
                                         is ObservableTransitionState.Transition -> {
-                                            if (state.fromScene == Scenes.Gone) {
+                                            if (state.fromContent == Scenes.Gone) {
                                                 true to "scene transitioning away from Gone"
                                             } else {
                                                 null
@@ -351,8 +357,8 @@
                             is ObservableTransitionState.Idle -> setOf(transitionState.currentScene)
                             is ObservableTransitionState.Transition ->
                                 setOf(
-                                    transitionState.fromScene,
-                                    transitionState.toScene,
+                                    transitionState.fromContent,
+                                    transitionState.toContent,
                                 )
                         }
                     val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen)
@@ -461,7 +467,8 @@
                     sceneInteractor.transitionState.value as? ObservableTransitionState.Transition
                         ?: return@collect
                 if (
-                    transition.fromScene == Scenes.Gone && transition.toScene == Scenes.Lockscreen
+                    transition.fromContent == Scenes.Gone &&
+                        transition.toContent == Scenes.Lockscreen
                 ) {
                     switchToScene(
                         targetSceneKey = Scenes.Gone,
@@ -479,7 +486,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
@@ -524,6 +531,37 @@
         }
     }
 
+    private fun handleDeviceEntryHapticsWhileDeviceLocked() {
+        applicationScope.launch {
+            deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
+                // Only check for haptics signals before device is entered
+                if (!isDeviceEntered) {
+                    coroutineScope {
+                        launch {
+                            deviceEntryHapticsInteractor.playSuccessHaptic
+                                .sample(sceneInteractor.currentScene)
+                                .collect { currentScene ->
+                                    vibratorHelper.vibrateAuthSuccess(
+                                        "$TAG, $currentScene device-entry::success"
+                                    )
+                                }
+                        }
+
+                        launch {
+                            deviceEntryHapticsInteractor.playErrorHaptic
+                                .sample(sceneInteractor.currentScene)
+                                .collect { currentScene ->
+                                    vibratorHelper.vibrateAuthError(
+                                        "$TAG, $currentScene device-entry::error"
+                                    )
+                                }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     /** Keeps [SysUiState] up-to-date */
     private fun hydrateSystemUiState() {
         applicationScope.launch {
@@ -570,15 +608,6 @@
         }
 
         applicationScope.launch {
-            sceneInteractor.currentScene
-                .map { it == Scenes.Bouncer }
-                .distinctUntilChanged()
-                .collect { isBouncerShowing ->
-                    windowController.setBouncerShowing(isBouncerShowing)
-                }
-        }
-
-        applicationScope.launch {
             occlusionInteractor.invisibleDueToOcclusion.collect { invisibleDueToOcclusion ->
                 windowController.setKeyguardOccluded(invisibleDueToOcclusion)
             }
@@ -694,8 +723,8 @@
                 .filterIsInstance<ObservableTransitionState.Transition>()
                 // Only consider user-initiated (e.g. drags) that go from bouncer to lockscreen.
                 .filter { transition ->
-                    transition.fromScene == Scenes.Bouncer &&
-                        transition.toScene == Scenes.Lockscreen &&
+                    transition.fromContent == Scenes.Bouncer &&
+                        transition.toContent == Scenes.Lockscreen &&
                         transition.isInitiatedByUserInput
                 }
                 .flatMapLatest { it.progress }
@@ -816,4 +845,8 @@
                 .collectLatest { deviceEntryInteractor.refreshLockscreenEnabled() }
         }
     }
+
+    companion object {
+        private const val TAG = "SceneContainerStartable"
+    }
 }
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 c6f51b3..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 &&
+                    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 &&
+                    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 cf1518e..fb53ddb 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
@@ -43,7 +44,7 @@
         )
     }
 
-    fun logSceneChangeRequested(
+    fun logSceneChanged(
         from: SceneKey,
         to: SceneKey,
         reason: String,
@@ -60,7 +61,7 @@
             },
             messagePrinter = {
                 buildString {
-                    append("Scene change requested: $str1 → $str2")
+                    append("Scene changed: $str1 → $str2")
                     if (isInstant) {
                         append(" (instant)")
                     }
@@ -70,21 +71,6 @@
         )
     }
 
-    fun logSceneChangeCommitted(
-        from: SceneKey,
-        to: SceneKey,
-    ) {
-        logBuffer.log(
-            tag = TAG,
-            level = LogLevel.INFO,
-            messageInitializer = {
-                str1 = from.toString()
-                str2 = to.toString()
-            },
-            messagePrinter = { "Scene change committed: $str1 → $str2" },
-        )
-    }
-
     fun logSceneTransition(transitionState: ObservableTransitionState) {
         when (transitionState) {
             is ObservableTransitionState.Transition -> {
@@ -92,8 +78,8 @@
                     tag = TAG,
                     level = LogLevel.INFO,
                     messageInitializer = {
-                        str1 = transitionState.fromScene.toString()
-                        str2 = transitionState.toScene.toString()
+                        str1 = transitionState.fromContent.toString()
+                        str2 = transitionState.toContent.toString()
                     },
                     messagePrinter = { "Scene transition started: $str1 → $str2" },
                 )
@@ -109,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,
@@ -130,7 +144,7 @@
         )
     }
 
-    fun logRemoteUserInteractionStarted(
+    fun logRemoteUserInputStarted(
         reason: String,
     ) {
         logBuffer.log(
@@ -141,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/Overlays.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Overlays.kt
new file mode 100644
index 0000000..c47a850
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Overlays.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.shared.model
+
+import com.android.compose.animation.scene.OverlayKey
+
+/**
+ * Keys of all known overlays.
+ *
+ * PLEASE KEEP THE KEYS SORTED ALPHABETICALLY.
+ */
+object Overlays {
+    /**
+     * The notifications shade overlay primarily shows a scrollable list of notifications.
+     *
+     * It's used only in the dual shade configuration, where there are two separate shades: one for
+     * notifications (this overlay) and another for [QuickSettingsShade].
+     *
+     * It's not used in the single/accordion configuration (swipe down once to reveal the shade,
+     * swipe down again the to expand quick settings) or in the "split" shade configuration (on
+     * large screens or unfolded foldables, where notifications and quick settings are shown
+     * side-by-side in their own columns).
+     */
+    @JvmField val NotificationsShade = OverlayKey("notifications_shade")
+
+    /**
+     * The quick settings shade overlay shows the quick settings tiles UI.
+     *
+     * It's used only in the dual shade configuration, where there are two separate shades: one for
+     * quick settings (this overlay) and another for [NotificationsShade].
+     *
+     * It's not used in the single/accordion configuration (swipe down once to reveal the shade,
+     * swipe down again the to expand quick settings) or in the "split" shade configuration (on
+     * large screens or unfolded foldables, where notifications and quick settings are shown
+     * side-by-side in their own columns).
+     */
+    @JvmField val QuickSettingsShade = OverlayKey("quick_settings_shade")
+}
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
deleted file mode 100644
index 61a06db..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ /dev/null
@@ -1,64 +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.scene.shared.model
-
-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.Activatable
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Defines interface for classes that can describe a "scene".
- *
- * In the scene framework, there can be multiple scenes in a single scene "container". The container
- * takes care of rendering the current scene and allowing scenes to be switched from one to another
- * based on either user action (for example, swiping down while on the lock screen scene may switch
- * to the shade scene).
- */
-interface Scene : Activatable {
-
-    /** 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.
-     *
-     * When the scene framework detects a user action, if the current scene has a map entry for that
-     * user action, the framework starts a transition to the scene in the map.
-     *
-     * Once the [Scene] becomes the current one, the scene framework will read this property and set
-     * up a collector to watch for new mapping values. If every map entry provided by the scene, the
-     * framework will set up user input handling for its [UserAction] and, if such a user action is
-     * detected, initiate a transition to the specified [UserActionResult].
-     *
-     * Note that reading from this method does _not_ mean that any user action has occurred.
-     * Instead, the property is read before any user action/gesture is detected so that the
-     * framework can decide whether to set up gesture/input detectors/listeners in case user actions
-     * of the given types ever occur.
-     *
-     * Note that a missing value for a specific [UserAction] means that the user action of the given
-     * type is not currently active in the scene and should be ignored by the framework, while the
-     * current scene is this one.
-     */
-    val destinationScenes: Flow<Map<UserAction, UserActionResult>>
-}
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/shared/model/Scenes.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
index ef5290f..115d664 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt
@@ -54,7 +54,9 @@
      * large screens or unfolded foldables, where notifications and quick settings are shown
      * side-by-side in their own columns).
      */
-    @JvmField val NotificationsShade = SceneKey("notifications_shade")
+    @Deprecated("The notifications shade scene has been replaced by an overlay")
+    @JvmField
+    val NotificationsShade = SceneKey("notifications_shade")
 
     /**
      * The quick settings scene shows the quick setting tiles.
@@ -83,7 +85,9 @@
      * large screens or unfolded foldables, where notifications and quick settings are shown
      * side-by-side in their own columns).
      */
-    @JvmField val QuickSettingsShade = SceneKey("quick_settings_shade")
+    @Deprecated("The quick settings shade scene has been replaced by an overlay")
+    @JvmField
+    val QuickSettingsShade = SceneKey("quick_settings_shade")
 
     /**
      * The shade is the scene that shows a scrollable list of notifications and the minimized
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
index be95441..b9f57f2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
@@ -27,14 +27,6 @@
     /** Reference to the gone/lockscreen to shade transition with split shade enabled. */
     val ToSplitShade = TransitionKey("GoneToSplitShade")
 
-    /** Reference to a scene transition that can collapse the shade scene instantly. */
-    val CollapseShadeInstantly = TransitionKey("CollapseShadeInstantly")
-
-    /**
-     * Reference to a scene transition that brings up the shade from the bottom instead of the top.
-     */
-    val OpenBottomShade = TransitionKey("OpenBottomShade")
-
     /**
      * Reference to a scene transition that can collapse the shade scene slightly faster than a
      * normal collapse would.
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..8a2e274 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
@@ -6,9 +6,10 @@
 import android.view.View
 import android.view.WindowInsets
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
-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.composable.Scene
 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..075599b 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
@@ -43,10 +44,10 @@
 import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-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.ComposableScene
+import com.android.systemui.scene.ui.composable.Overlay
+import com.android.systemui.scene.ui.composable.Scene
 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 +71,7 @@
         containerConfig: SceneContainerConfig,
         sharedNotificationContainer: SharedNotificationContainer,
         scenes: Set<Scene>,
+        overlays: Set<Overlay>,
         onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
         dataSourceDelegator: SceneDataSourceDelegator,
         alternateBouncerDependencies: AlternateBouncerDependencies,
@@ -86,8 +88,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 +128,7 @@
                                 viewModel = viewModel,
                                 windowInsets = windowInsets,
                                 sceneByKey = sortedSceneByKey,
+                                overlayByKey = sortedOverlayByKey,
                                 dataSourceDelegator = dataSourceDelegator,
                                 containerConfig = containerConfig,
                             )
@@ -156,6 +173,7 @@
         viewModel: SceneContainerViewModel,
         windowInsets: StateFlow<WindowInsets?>,
         sceneByKey: Map<SceneKey, Scene>,
+        overlayByKey: Map<OverlayKey, Overlay>,
         dataSourceDelegator: SceneDataSourceDelegator,
         containerConfig: SceneContainerConfig,
     ): View {
@@ -168,8 +186,8 @@
                     ) {
                         SceneContainer(
                             viewModel = viewModel,
-                            sceneByKey =
-                                sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
+                            sceneByKey = sceneByKey,
+                            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
deleted file mode 100644
index b707a5a..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneActionsViewModel.kt
+++ /dev/null
@@ -1,92 +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.scene.ui.viewmodel
-
-import androidx.compose.ui.Alignment
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade
-import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-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
-@AssistedInject
-constructor(
-    private val shadeInteractor: ShadeInteractor,
-) : SceneActionsViewModel() {
-
-    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
-        shadeInteractor.shadeMode
-            .map { shadeMode ->
-                buildMap<UserAction, UserActionResult> {
-                    if (
-                        shadeMode is ShadeMode.Single ||
-                            // TODO(b/338577208): Remove this once we add Dual Shade invocation
-                            // zones.
-                            shadeMode is ShadeMode.Dual
-                    ) {
-                        if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
-                            put(
-                                Swipe(
-                                    pointerCount = 2,
-                                    fromSource = Edge.Bottom,
-                                    direction = SwipeDirection.Up,
-                                ),
-                                UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
-                            )
-                        } else {
-                            put(
-                                Swipe(
-                                    pointerCount = 2,
-                                    fromSource = Edge.Top,
-                                    direction = SwipeDirection.Down,
-                                ),
-                                UserActionResult(SceneFamilies.QuickSettings)
-                            )
-                        }
-                    }
-
-                    if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
-                        put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
-                    } else {
-                        put(
-                            Swipe.Down,
-                            UserActionResult(
-                                SceneFamilies.NotifShade,
-                                ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
-                            )
-                        )
-                    }
-                }
-            }
-            .collectLatest { setActions(it) }
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(): GoneSceneActionsViewModel
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
new file mode 100644
index 0000000..ea4122a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.viewmodel
+
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.map
+
+class GoneUserActionsViewModel
+@AssistedInject
+constructor(
+    private val shadeInteractor: ShadeInteractor,
+) : UserActionsViewModel() {
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        shadeInteractor.shadeMode
+            .map { shadeMode ->
+                buildMap<UserAction, UserActionResult> {
+                    if (
+                        shadeMode is ShadeMode.Single ||
+                            // TODO(b/338577208): Remove this once we add Dual Shade invocation
+                            // zones.
+                            shadeMode is ShadeMode.Dual
+                    ) {
+                        put(
+                            Swipe(
+                                pointerCount = 2,
+                                fromSource = Edge.Top,
+                                direction = SwipeDirection.Down,
+                            ),
+                            UserActionResult(SceneFamilies.QuickSettings)
+                        )
+                    }
+
+                    put(
+                        Swipe.Down,
+                        UserActionResult(
+                            SceneFamilies.NotifShade,
+                            ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+                        )
+                    )
+                }
+            }
+            .collect { setActions(it) }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): GoneUserActionsViewModel
+    }
+}
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 7f7f0f1..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,14 +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
 
@@ -42,21 +44,17 @@
     private val sceneInteractor: SceneInteractor,
     private val falsingInteractor: FalsingInteractor,
     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 {
@@ -73,7 +71,8 @@
                     }
                 }
             )
-            awaitCancellation()
+
+            hydrator.activate()
         } finally {
             // Clears the previously-sent MotionEventHandler so the owner of the view-model releases
             // their reference to it.
@@ -91,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.
      */
@@ -102,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.
      *
@@ -135,16 +146,29 @@
                 else -> null
             }
 
-        return interactionTypeOrNull?.let { interactionType ->
-            // It's important that the falsing system is always queried, even if no enforcement will
-            // occur. This helps build up the right signal in the system.
-            val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
+        val fromScene = currentScene.value
+        val isAllowed =
+            interactionTypeOrNull?.let { interactionType ->
+                // It's important that the falsing system is always queried, even if no enforcement
+                // will occur. This helps build up the right signal in the system.
+                val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
 
-            // Only enforce falsing if moving from the lockscreen scene to a new scene.
-            val fromLockscreenScene = currentScene.value == Scenes.Lockscreen
+                // Only enforce falsing if moving from the lockscreen scene to a new scene.
+                val fromLockscreenScene = fromScene == Scenes.Lockscreen
 
-            !fromLockscreenScene || !isFalseTouch
-        } ?: true
+                !fromLockscreenScene || !isFalseTouch
+            } ?: true
+
+        if (isAllowed) {
+            // A scene change is guaranteed; log it.
+            logger.logSceneChanged(
+                from = fromScene,
+                to = toScene,
+                reason = "user interaction",
+                isInstant = false,
+            )
+        }
+        return isAllowed
     }
 
     /**
@@ -155,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/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModel.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModel.kt
index b5de1b6..57628d0 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/UserActionsViewModel.kt
@@ -18,21 +18,20 @@
 
 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
 import kotlinx.coroutines.flow.asStateFlow
 
 /**
- * Base class for view-models that need to keep a map of scene actions (also known as "destination
- * scenes") up-to-date.
+ * Base class for view-models that need to keep a map of user actions up-to-date.
  *
  * Subclasses need only to override [hydrateActions], suspending forever if they need; they don't
  * 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 UserActionsViewModel : ExclusiveActivatable() {
 
     private val _actions = MutableStateFlow<Map<UserAction, UserActionResult>>(emptyMap())
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
index 474afa8b..56afb79 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -9,7 +9,6 @@
 import android.view.ViewTreeObserver
 import android.view.animation.AccelerateDecelerateInterpolator
 import androidx.constraintlayout.widget.Guideline
-import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.res.R
 import com.android.systemui.screenshot.message.ProfileMessageController
@@ -49,44 +48,19 @@
     }
 
     fun onScreenshotTaken(screenshot: ScreenshotData) {
-        if (screenshotPrivateProfileBehaviorFix()) {
-            mainScope.launch {
-                val profileData = profileMessageController.onScreenshotTaken(screenshot.userHandle)
-                var notifiedApps: List<CharSequence> =
-                    screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
-
-                // If profile first run needs to show, bias towards that, otherwise show screenshot
-                // detection notification if needed.
-                if (profileData != null) {
-                    workProfileFirstRunView.visibility = View.VISIBLE
-                    detectionNoticeView.visibility = View.GONE
-                    profileMessageController.bindView(workProfileFirstRunView, profileData) {
-                        animateOutMessageContainer()
-                    }
-                    animateInMessageContainer()
-                } else if (notifiedApps.isNotEmpty()) {
-                    detectionNoticeView.visibility = View.VISIBLE
-                    workProfileFirstRunView.visibility = View.GONE
-                    screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
-                    animateInMessageContainer()
-                }
-            }
-        } else {
-            val workProfileData =
-                workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
+        mainScope.launch {
+            val profileData = profileMessageController.onScreenshotTaken(screenshot.userHandle)
             var notifiedApps: List<CharSequence> =
                 screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
 
-            // If work profile first run needs to show, bias towards that, otherwise show screenshot
+            // If profile first run needs to show, bias towards that, otherwise show screenshot
             // detection notification if needed.
-            if (workProfileData != null) {
+            if (profileData != null) {
                 workProfileFirstRunView.visibility = View.VISIBLE
                 detectionNoticeView.visibility = View.GONE
-                workProfileMessageController.populateView(
-                    workProfileFirstRunView,
-                    workProfileData,
-                    this::animateOutMessageContainer
-                )
+                profileMessageController.bindView(workProfileFirstRunView, profileData) {
+                    animateOutMessageContainer()
+                }
                 animateInMessageContainer()
             } else if (notifiedApps.isNotEmpty()) {
                 detectionNoticeView.visibility = View.VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
deleted file mode 100644
index 922997d..0000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2022 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.screenshot
-
-import android.util.Log
-import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-
-/** Implementation of [ScreenshotRequestProcessor] */
-class RequestProcessor(
-    private val capture: ImageCapture,
-    private val policy: ScreenshotPolicy,
-) : ScreenshotRequestProcessor {
-
-    override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
-        var result = screenshot
-
-        // Apply work profile screenshots policy:
-        //
-        // If the focused app belongs to a work profile, transforms a full screen
-        // (or partial) screenshot request to a task snapshot (provided image) screenshot.
-
-        // Whenever displayContentInfo is fetched, the topComponent is also populated
-        // regardless of the managed profile status.
-
-        if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
-            val info = policy.findPrimaryContent(screenshot.displayId)
-            Log.d(TAG, "findPrimaryContent: $info")
-            result.taskId = info.taskId
-            result.topComponent = info.component
-            result.userHandle = info.user
-
-            if (policy.isManagedProfile(info.user.identifier)) {
-                val image =
-                    capture.captureTask(info.taskId)
-                        ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!")
-
-                // Provide the task snapshot as the screenshot
-                result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
-                result.bitmap = image
-                result.screenBounds = info.bounds
-            }
-        }
-
-        return result
-    }
-}
-
-private const val TAG = "RequestProcessor"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
index 3ad4075a..ee1008d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
@@ -27,5 +27,5 @@
     suspend fun process(original: ScreenshotData): ScreenshotData
 }
 
-/** Exception thrown by [RequestProcessor] if something goes wrong. */
+/** Exception thrown by [ScreenshotRequestProcessor] if something goes wrong. */
 class RequestProcessorException(message: String) : IllegalStateException(message)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 50ea3bb..448f7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -99,6 +99,9 @@
     ) {
         val displays = getDisplaysToScreenshot(screenshotRequest.type)
         val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
+        if (displays.isEmpty()) {
+            Log.wtf(TAG, "No displays found for screenshot.")
+        }
         displays.forEach { display ->
             val displayId = display.displayId
             var screenshotHandler: ScreenshotHandler =
@@ -219,8 +222,7 @@
     }
 
     private fun getScreenshotController(display: Display): InteractiveScreenshotHandler {
-        val controller =
-            screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
+        val controller = screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
         screenshotController = controller
         return controller
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index 9db1f24..ad5e772 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -71,7 +71,9 @@
 import com.android.systemui.screenshot.scroll.CropView;
 import com.android.systemui.settings.UserTracker;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -344,10 +346,63 @@
 
         // Set up the dropdown when multiple backlinks are available.
         if (backlinksData.size() > 1) {
-            setUpListPopupWindow(backlinksData, mBacklinksDataTextView);
+            setUpListPopupWindow(updateBacklinkLabelsWithDuplicateNames(backlinksData),
+                    mBacklinksDataTextView);
         }
     }
 
+    /**
+     * If there are more than 1 backlinks that have the same app name, then this method appends
+     * a numerical suffix to such backlinks to help users distinguish.
+     */
+    private List<InternalBacklinksData> updateBacklinkLabelsWithDuplicateNames(
+            List<InternalBacklinksData> backlinksData) {
+        // Check if there are multiple backlinks with same name.
+        Map<String, Integer> duplicateNamedBacklinksCountMap = new HashMap<>();
+        for (InternalBacklinksData data : backlinksData) {
+            if (duplicateNamedBacklinksCountMap.containsKey(data.getDisplayLabel())) {
+                int duplicateCount = duplicateNamedBacklinksCountMap.get(data.getDisplayLabel());
+                if (duplicateCount == 0) {
+                    // If this is the first time the loop is coming across a duplicate name, set the
+                    // count to 2. This way the count starts from 1 for all duplicate named
+                    // backlinks.
+                    duplicateNamedBacklinksCountMap.put(data.getDisplayLabel(), 2);
+                } else {
+                    // For all duplicate named backlinks, increase the duplicate count by 1.
+                    duplicateNamedBacklinksCountMap.put(data.getDisplayLabel(), duplicateCount + 1);
+                }
+            } else {
+                // This is the first time the loop is coming across a backlink with this name. Set
+                // its count to 0. The loop will increase its count by 1 when a duplicate is found.
+                duplicateNamedBacklinksCountMap.put(data.getDisplayLabel(), 0);
+            }
+        }
+
+        // Go through the backlinks in reverse order as it is easier to assign the numerical suffix
+        // in descending order of frequency using the duplicate map that was built earlier. For
+        // example, if "App A" is present 3 times, then we assign display label "App A (3)" first
+        // and then "App A (2)", lastly "App A (1)".
+        for (InternalBacklinksData data : backlinksData.reversed()) {
+            String originalBacklinkLabel = data.getDisplayLabel();
+            int duplicateCount = duplicateNamedBacklinksCountMap.get(originalBacklinkLabel);
+
+            // The display label should only be updated if there are multiple backlinks with the
+            // same name.
+            if (duplicateCount > 0) {
+                // Update the display label to: "App name (count)"
+                data.setDisplayLabel(
+                        getString(R.string.backlinks_duplicate_label_format, originalBacklinkLabel,
+                                duplicateCount));
+
+                // Decrease the duplicate count and update the map.
+                duplicateCount--;
+                duplicateNamedBacklinksCountMap.put(originalBacklinkLabel, duplicateCount);
+            }
+        }
+
+        return backlinksData;
+    }
+
     private void setUpListPopupWindow(List<InternalBacklinksData> backlinksData, View anchor) {
         ListPopupWindow listPopupWindow = new ListPopupWindow(this);
         listPopupWindow.setAnchorView(anchor);
@@ -365,7 +420,7 @@
             public View getView(int position, @Nullable View convertView, ViewGroup parent) {
                 TextView itemView = (TextView) super.getView(position, convertView, parent);
                 InternalBacklinksData data = backlinksData.get(position);
-                itemView.setText(data.getClipData().getDescription().getLabel());
+                itemView.setText(data.getDisplayLabel());
 
                 Drawable icon = data.getAppIcon();
                 icon.setBounds(createBacklinksTextViewDrawableBounds());
@@ -387,7 +442,7 @@
      * expected to be already set when this method is called.
      */
     private void updateBacklinksTextView(InternalBacklinksData backlinksData) {
-        mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel());
+        mBacklinksDataTextView.setText(backlinksData.getDisplayLabel());
         Drawable appIcon = backlinksData.getAppIcon();
         Rect compoundDrawableBounds = createBacklinksTextViewDrawableBounds();
         appIcon.setBounds(compoundDrawableBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
index 0e312f9..30c33c5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
@@ -20,4 +20,6 @@
 import android.graphics.drawable.Drawable
 
 /** A class to hold the [ClipData] for backlinks and the corresponding app's [Drawable] icon. */
-internal data class InternalBacklinksData(val clipData: ClipData, val appIcon: Drawable)
+internal data class InternalBacklinksData(val clipData: ClipData, val appIcon: Drawable) {
+    var displayLabel: String = clipData.description.label.toString()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
index 44f767a..2cb9fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
@@ -19,14 +19,11 @@
 import android.content.ComponentName
 import android.content.Context
 import android.os.Process
-import com.android.systemui.Flags.screenshotPrivateProfileBehaviorFix
 import com.android.systemui.SystemUIService
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.screenshot.ImageCapture
-import com.android.systemui.screenshot.RequestProcessor
-import com.android.systemui.screenshot.ScreenshotPolicy
 import com.android.systemui.screenshot.ScreenshotRequestProcessor
 import com.android.systemui.screenshot.data.repository.DisplayContentRepository
 import com.android.systemui.screenshot.data.repository.DisplayContentRepositoryImpl
@@ -68,23 +65,18 @@
             @Application context: Context,
             @Background background: CoroutineDispatcher,
             imageCapture: ImageCapture,
-            policyProvider: Provider<ScreenshotPolicy>,
-            displayContentRepoProvider: Provider<DisplayContentRepository>,
+            displayContentRepo: DisplayContentRepository,
             policyListProvider: Provider<List<CapturePolicy>>,
         ): ScreenshotRequestProcessor {
-            return if (screenshotPrivateProfileBehaviorFix()) {
-                PolicyRequestProcessor(
-                    background = background,
-                    capture = imageCapture,
-                    displayTasks = displayContentRepoProvider.get(),
-                    policies = policyListProvider.get(),
-                    defaultOwner = Process.myUserHandle(),
-                    defaultComponent =
-                        ComponentName(context.packageName, SystemUIService::class.java.toString())
-                )
-            } else {
-                RequestProcessor(imageCapture, policyProvider.get())
-            }
+            return PolicyRequestProcessor(
+                background = background,
+                capture = imageCapture,
+                displayTasks = displayContentRepo,
+                policies = policyListProvider.get(),
+                defaultOwner = Process.myUserHandle(),
+                defaultComponent =
+                    ComponentName(context.packageName, SystemUIService::class.java.toString())
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index 3fe3162..29450a2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
 import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
 import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import javax.inject.Inject
 import kotlinx.coroutines.flow.first
 
@@ -48,7 +48,7 @@
             return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
         }
 
-        if (DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context)) {
+        if (DesktopModeStatus.canEnterDesktopMode(context)) {
             content.rootTasks.firstOrNull()?.also {
                 if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
                     return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index ee1944e..ad27da9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -214,8 +214,7 @@
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
-                if (mCurrentDraggingBoundary != CropBoundary.NONE
-                        && mActivePointerId == event.getPointerId(mActivePointerId)) {
+                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                     updateListener(MotionEvent.ACTION_UP, event.getX(0));
                     return true;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
index e7ee961..edff4bf 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.settings
 
 import android.view.Display
+import com.android.systemui.util.annotations.WeaklyReferencedCallback
 import java.util.concurrent.Executor
 
 /**
@@ -52,6 +53,7 @@
     fun getDisplay(displayId: Int): Display
 
     /** Ćallback for notifying of changes. */
+    @WeaklyReferencedCallback
     interface Callback {
 
         /** Notifies that a display has been added. */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
index 05f19ef..b9f9b92 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/MultiUserUtilsModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.settings;
 
-import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
@@ -67,7 +66,7 @@
             @Background CoroutineDispatcher backgroundDispatcher,
             @Background Handler handler
     ) {
-        int startingUser = ActivityManager.getCurrentUser();
+        int startingUser = userManager.getBootUser().getIdentifier();
         UserTrackerImpl tracker = new UserTrackerImpl(context, featureFlagsProvider, userManager,
                 iActivityManager, dumpManager, appScope, backgroundDispatcher, handler);
         tracker.initialize(startingUser);
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..7fa9926 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, TAG)
 
     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)
@@ -264,10 +301,11 @@
 
         if (touchMonitor == null) {
             touchMonitor =
-                ambientTouchComponentFactory.create(this, HashSet()).getTouchMonitor().apply {
+                ambientTouchComponentFactory.create(this, HashSet(), TAG).getTouchMonitor().apply {
                     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")
     }
 
     /**
@@ -460,22 +508,32 @@
     fun onTouchEvent(ev: MotionEvent): Boolean {
         SceneContainerFlag.assertInLegacyMode()
 
-        // 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()
-                    ))
-        ) {
+        if (communalContainerView == null) {
+            // Return early so we don't log unnecessarily and fill up our LogBuffer.
             return false
         }
 
-        return communalContainerView?.let { handleTouchEventOnCommunalView(ev) } ?: false
+        // 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.
+        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
+        }
+
+        return handleTouchEventOnCommunalView(ev)
     }
 
     private fun handleTouchEventOnCommunalView(ev: MotionEvent): Boolean {
@@ -484,15 +542,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 +615,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
@@ -546,4 +635,8 @@
 
     override val lifecycle: Lifecycle
         get() = lifecycleRegistry
+
+    companion object {
+        private const val TAG = "GlanceableHubContainer"
+    }
 }
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/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 7e0454c..3f3ad13 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -49,6 +49,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.Flags;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.dagger.SysUISingleton;
@@ -342,6 +343,12 @@
                     this::setKeyguardOccluded
             );
         }
+        if (ComposeBouncerFlags.INSTANCE.isComposeBouncerOrSceneContainerEnabled()) {
+            collectFlow(mWindowRootView, mNotificationShadeWindowModel.isBouncerShowing(),
+                    this::setBouncerShowing);
+            collectFlow(mWindowRootView, mNotificationShadeWindowModel.getDoesBouncerRequireIme(),
+                    this::setKeyguardNeedsInput);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 16aef65..830649b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -1005,7 +1005,7 @@
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
         if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
-            mDeviceEntryFaceAuthInteractor.onQsExpansionStared();
+            mDeviceEntryFaceAuthInteractor.onShadeExpansionStarted();
         }
     }
 
@@ -1063,13 +1063,17 @@
         mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
         setClippingBounds();
 
-        if (mSplitShadeEnabled) {
-            // In split shade we want to pretend that QS are always collapsed so their behaviour and
-            // interactions don't influence notifications as they do in portrait. But we want to set
-            // 0 explicitly in case we're rotating from non-split shade with QS expansion of 1.
-            mNotificationStackScrollLayoutController.setQsExpansionFraction(0);
-        } else {
-            mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
+        if (!SceneContainerFlag.isEnabled()) {
+            if (mSplitShadeEnabled) {
+                // In split shade we want to pretend that QS are always collapsed so their
+                // behaviour and interactions don't influence notifications as they do in portrait.
+                // But we want to set 0 explicitly in case we're rotating from non-split shade with
+                // QS expansion of 1.
+                mNotificationStackScrollLayoutController.setQsExpansionFraction(0);
+            } else {
+                mNotificationStackScrollLayoutController.setQsExpansionFraction(
+                        qsExpansionFraction);
+            }
         }
 
         mDepthController.setQsPanelExpansion(qsExpansionFraction);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 23e2620..5d03a28 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade
 
 import android.view.MotionEvent
-import androidx.compose.ui.Alignment
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -25,7 +24,6 @@
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
 import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -177,7 +175,6 @@
         sceneInteractor.changeScene(
             SceneFamilies.NotifShade,
             "ShadeController.animateExpandShade",
-            OpenBottomShade.takeIf { shadeInteractor.shadeAlignment == Alignment.BottomEnd }
         )
     }
 
@@ -185,7 +182,6 @@
         sceneInteractor.changeScene(
             SceneFamilies.QuickSettings,
             "ShadeController.animateExpandQs",
-            OpenBottomShade.takeIf { shadeInteractor.shadeAlignment == Alignment.BottomEnd }
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 606fef0..fc8a593 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
@@ -35,9 +37,10 @@
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-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.composable.Scene
 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/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index a4fed873..193056c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -18,7 +18,6 @@
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -103,9 +102,6 @@
     @Deprecated("Use ShadeInteractor.isQsBypassingShade instead")
     val legacyExpandImmediate: StateFlow<Boolean>
 
-    /** Whether dual shade should be aligned to the bottom (true) or to the top (false). */
-    val isDualShadeAlignedToBottom: Boolean
-
     /**
      * Whether the shade layout should be wide (true) or narrow (false).
      *
@@ -238,9 +234,6 @@
     private val _isShadeLayoutWide = MutableStateFlow(false)
     override val isShadeLayoutWide: StateFlow<Boolean> = _isShadeLayoutWide.asStateFlow()
 
-    override val isDualShadeAlignedToBottom =
-        applicationContext.resources.getBoolean(R.bool.config_dualShadeAlignedToBottom)
-
     override fun setShadeLayoutWide(isShadeLayoutWide: Boolean) {
         _isShadeLayoutWide.value = isShadeLayoutWide
     }
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 8006e94..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 ->
+                is ObservableTransitionState.Transition.ChangeScene ->
                     when {
                         state.fromScene == Scenes.Gone ->
                             if (state.toScene.isExpandable()) {
@@ -88,6 +88,9 @@
                             }
                         else -> flowOf(1f)
                     }
+                is ObservableTransitionState.Transition.ShowOrHideOverlay,
+                is ObservableTransitionState.Transition.ReplaceOverlay ->
+                    TODO("b/359173565: Handle overlay transitions")
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
index 79a94a5..8467185 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -49,10 +49,10 @@
                     is ObservableTransitionState.Idle -> flowOf(false)
                     is ObservableTransitionState.Transition ->
                         if (
-                            (state.fromScene == Scenes.Shade &&
-                                state.toScene != Scenes.QuickSettings) ||
-                                (state.fromScene == Scenes.QuickSettings &&
-                                    state.toScene != Scenes.Shade)
+                            (state.fromContent == Scenes.Shade &&
+                                state.toContent != Scenes.QuickSettings) ||
+                                (state.fromContent == Scenes.QuickSettings &&
+                                    state.toContent != Scenes.Shade)
                         ) {
                             state.isUserInputOngoing.map { !it }
                         } else {
@@ -71,10 +71,10 @@
                     is ObservableTransitionState.Transition ->
                         if (
                             state.isInitiatedByUserInput &&
-                                (state.fromScene == Scenes.Shade ||
-                                    state.toScene == Scenes.Shade ||
-                                    state.fromScene == Scenes.QuickSettings ||
-                                    state.toScene == Scenes.QuickSettings)
+                                (state.fromContent == Scenes.Shade ||
+                                    state.toContent == Scenes.Shade ||
+                                    state.fromContent == Scenes.QuickSettings ||
+                                    state.toContent == Scenes.QuickSettings)
                         ) {
                             state.isUserInputOngoing.map { !it }
                         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 45f359e..73e86a2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.shade.domain.interactor
 
-import com.android.systemui.shade.shared.model.ShadeAlignment
 import com.android.systemui.shade.shared.model.ShadeMode
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -70,9 +69,6 @@
      * wide as the entire screen.
      */
     val isShadeLayoutWide: StateFlow<Boolean>
-
-    /** How to align the shade content. */
-    val shadeAlignment: ShadeAlignment
 }
 
 /** ShadeInteractor methods with implementations that differ between non-empty impls. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index e77aca9..d51fd28 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.shade.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shade.shared.model.ShadeAlignment
 import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -48,5 +47,4 @@
     override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
     override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single)
     override val isShadeLayoutWide: StateFlow<Boolean> = inactiveFlowBoolean
-    override val shadeAlignment: ShadeAlignment = ShadeAlignment.Top
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index d64b21f..3552092 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.shared.flag.DualShade
-import com.android.systemui.shade.shared.model.ShadeAlignment
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.phone.DozeParameters
@@ -114,15 +113,6 @@
                 initialValue = determineShadeMode(isShadeLayoutWide.value)
             )
 
-    override val shadeAlignment: ShadeAlignment
-        get() {
-            return if (shadeRepository.isDualShadeAlignedToBottom) {
-                ShadeAlignment.Bottom
-            } else {
-                ShadeAlignment.Top
-            }
-        }
-
     override val isExpandToQsEnabled: Flow<Boolean> =
         combine(
             disableFlagsRepository.disableFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 6a21531..e84cfa5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -90,8 +90,8 @@
                         when (state) {
                             is ObservableTransitionState.Idle -> false
                             is ObservableTransitionState.Transition ->
-                                state.toScene == quickSettingsScene &&
-                                    state.fromScene != notificationsScene
+                                state.toContent == quickSettingsScene &&
+                                    state.fromContent != notificationsScene
                         }
                     }
                     .distinctUntilChanged()
@@ -99,14 +99,17 @@
             .distinctUntilChanged()
 
     override val isQsFullscreen: Flow<Boolean> =
-        sceneInteractor
-            .resolveSceneFamily(SceneFamilies.QuickSettings)
-            .flatMapLatestConflated { quickSettingsScene ->
+        combine(
+                shadeRepository.isShadeLayoutWide,
+                sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
+                ::Pair
+            )
+            .flatMapLatestConflated { (isShadeLayoutWide, quickSettingsScene) ->
                 sceneInteractor.transitionState
                     .map { state ->
                         when (state) {
                             is ObservableTransitionState.Idle ->
-                                state.currentScene == quickSettingsScene
+                                !isShadeLayoutWide && state.currentScene == quickSettingsScene
                             is ObservableTransitionState.Transition -> false
                         }
                     }
@@ -147,9 +150,9 @@
                                     flowOf(0f)
                                 }
                             is ObservableTransitionState.Transition ->
-                                if (state.toScene == resolvedSceneKey) {
+                                if (state.toContent == resolvedSceneKey) {
                                     state.progress
-                                } else if (state.fromScene == resolvedSceneKey) {
+                                } else if (state.fromContent == resolvedSceneKey) {
                                     state.progress.map { progress -> 1 - progress }
                                 } else {
                                     flowOf(0f)
@@ -172,8 +175,8 @@
                     is ObservableTransitionState.Transition ->
                         sceneInteractor.resolveSceneFamily(sceneKey).map { resolvedSceneKey ->
                             state.isInitiatedByUserInput &&
-                                (state.toScene == resolvedSceneKey ||
-                                    state.fromScene == resolvedSceneKey)
+                                (state.toContent == resolvedSceneKey ||
+                                    state.fromContent == resolvedSceneKey)
                         }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeAlignment.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeAlignment.kt
deleted file mode 100644
index 06905379..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeAlignment.kt
+++ /dev/null
@@ -1,27 +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.shade.shared.model
-
-/** Enumerates all supported alignments of the shade. */
-sealed interface ShadeAlignment {
-
-    /** Aligns the shade to the top. */
-    data object Top : ShadeAlignment
-
-    /** Aligns the shade to the bottom. */
-    data object Bottom : ShadeAlignment
-}
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..9655d92 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
@@ -16,14 +16,25 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 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.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.BooleanFlowOperators.any
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
 
 /** Models UI state for the shade window. */
@@ -32,11 +43,92 @@
 @Inject
 constructor(
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    keyguardInteractor: KeyguardInteractor,
+    sceneInteractor: dagger.Lazy<SceneInteractor>,
+    authenticationInteractor: dagger.Lazy<AuthenticationInteractor>,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
 ) {
+    /**
+     * 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()
+
+    /**
+     * Whether bouncer is currently showing or not.
+     *
+     * Applicable only when either [SceneContainerFlag] or [ComposeBouncerFlags] are enabled,
+     * otherwise it throws an error.
+     */
+    val isBouncerShowing: Flow<Boolean> =
+        when {
+            SceneContainerFlag.isEnabled -> {
+                sceneInteractor.get().transitionState.map { it.isIdle(Scenes.Bouncer) }
+            }
+            ComposeBouncerFlags.isOnlyComposeBouncerEnabled() -> primaryBouncerInteractor.isShowing
+            else ->
+                flow {
+                    error(
+                        "Consume this flow only when SceneContainerFlag " +
+                            "or ComposeBouncerFlags are enabled"
+                    )
+                }
+        }.distinctUntilChanged()
+
+    /**
+     * Whether the bouncer currently require IME for device entry.
+     *
+     * This emits true when the authentication method is set to password and the bouncer is
+     * currently showing. Throws an error when this is used without either [SceneContainerFlag] or
+     * [ComposeBouncerFlags]
+     */
+    val doesBouncerRequireIme: Flow<Boolean> =
+        if (ComposeBouncerFlags.isComposeBouncerOrSceneContainerEnabled()) {
+                // This is required to make the window, where the bouncer resides,
+                // focusable. InputMethodManager allows IME to be shown only for views
+                // in windows that do not have the FLAG_NOT_FOCUSABLE flag.
+
+                isBouncerShowing
+                    .sample(authenticationInteractor.get().authenticationMethod, ::Pair)
+                    .map { (showing, authMethod) ->
+                        showing && authMethod == AuthenticationMethodModel.Password
+                    }
+            } else {
+                flow {
+                    error(
+                        "Consume this flow only when SceneContainerFlag " +
+                            "or ComposeBouncerFlags are enabled"
+                    )
+                }
+            }
+            .distinctUntilChanged()
 }
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
deleted file mode 100644
index 00c0235..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
+++ /dev/null
@@ -1,67 +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.shade.ui.viewmodel
-
-import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.lifecycle.SysUiViewModel
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import kotlinx.coroutines.awaitCancellation
-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
- * overlay on top of another scene UI.
- */
-class OverlayShadeViewModel
-@AssistedInject
-constructor(private val sceneInteractor: SceneInteractor, shadeInteractor: ShadeInteractor) :
-    SysUiViewModel() {
-    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()
-
-    /** Dictates the alignment of the overlay shade panel on the screen. */
-    val panelAlignment = shadeInteractor.shadeAlignment
-
-    override suspend fun onActivated(): Nothing {
-        sceneInteractor.resolveSceneFamily(SceneFamilies.Home).collectLatest { sceneKey ->
-            _backgroundScene.value = sceneKey
-        }
-        awaitCancellation()
-    }
-
-    /** Notifies that the user has clicked the semi-transparent background scrim. */
-    fun onScrimClicked() {
-        sceneInteractor.changeScene(
-            toScene = SceneFamilies.Home,
-            loggingReason = "Shade scrim clicked",
-        )
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(): OverlayShadeViewModel
-    }
-}
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/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index fe3bcb5..ce4c081 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,17 +35,15 @@
 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.
  *
- * Different from [ShadeSceneActionsViewModel], which only models user actions that can be performed
+ * Different from [ShadeUserActionsViewModel], which only models user actions that can be performed
  * to navigate to other scenes.
  */
 class ShadeSceneContentViewModel
@@ -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/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
index bdc0fdb..f8a850a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
@@ -24,12 +24,11 @@
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 
 /**
@@ -37,12 +36,12 @@
  *
  * Different from the [ShadeSceneContentViewModel] which models the _content_ of the scene.
  */
-class ShadeSceneActionsViewModel
+class ShadeUserActionsViewModel
 @AssistedInject
 constructor(
     private val qsSceneAdapter: QSSceneAdapter,
     private val shadeInteractor: ShadeInteractor,
-) : SceneActionsViewModel() {
+) : UserActionsViewModel() {
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
         combine(
@@ -67,11 +66,11 @@
                     }
                 }
             }
-            .collectLatest { actions -> setActions(actions) }
+            .collect { actions -> setActions(actions) }
     }
 
     @AssistedFactory
     interface Factory {
-        fun create(): ShadeSceneActionsViewModel
+        fun create(): ShadeUserActionsViewModel
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
index 922560f..0e1bf72 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.smartspace.config
 
+import com.android.systemui.Flags.smartspaceViewpager2
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.plugins.BcSmartspaceConfigPlugin
 
@@ -23,4 +24,7 @@
     BcSmartspaceConfigPlugin {
     override val isDefaultDateWeatherDisabled: Boolean
         get() = true
+
+    override val isViewPager2Enabled: Boolean
+        get() = smartspaceViewpager2()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index a1477b5..f88fd7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -1174,7 +1174,7 @@
         }
     }
 
-    @Override
+    // This was previously called from WM, but is now called from WMShell
     public void onRecentsAnimationStateChanged(boolean running) {
         synchronized (mLock) {
             mHandler.obtainMessage(MSG_RECENTS_ANIMATION_STATE_CHANGED, running ? 1 : 0, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 693cc4a..5ef5a7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -28,6 +28,9 @@
 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
 
+import static com.android.systemui.Flags.enableViewCaptureTracing;
+import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
+
 import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
@@ -73,12 +76,17 @@
 import android.widget.FrameLayout;
 import android.widget.RelativeLayout;
 
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Lazy;
+
 import javax.inject.Inject;
 
 /**
@@ -100,17 +108,18 @@
     private Context mDisplayContext;
     private final Context mSysUiContext;
     private final Handler mHandler = new H(Looper.getMainLooper());
+    private final Handler mBackgroundHandler;
     private long mShowDelayMs = 0L;
     private final IBinder mWindowToken = new Binder();
     private final CommandQueue mCommandQueue;
 
     private ClingWindowView mClingWindow;
-    /** The last {@link WindowManager} that is used to add the confirmation window. */
+    /** The wrapper on the last {@link WindowManager} used to add the confirmation window. */
     @Nullable
-    private WindowManager mWindowManager;
+    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
     /**
-     * The WindowContext that is registered with {@link #mWindowManager} with options to specify the
-     * {@link RootDisplayArea} to attach the confirmation window.
+     * The WindowContext that is registered with {@link #mViewCaptureAwareWindowManager} with
+     * options to specify the {@link RootDisplayArea} to attach the confirmation window.
      */
     @Nullable
     private Context mWindowContext;
@@ -127,15 +136,21 @@
 
     private ContentObserver mContentObserver;
 
+    private Lazy<ViewCapture> mLazyViewCapture;
+
     @Inject
     public ImmersiveModeConfirmation(Context context, CommandQueue commandQueue,
-                                     SecureSettings secureSettings) {
+                                     SecureSettings secureSettings,
+                                     dagger.Lazy<ViewCapture> daggerLazyViewCapture,
+                                     @Background Handler backgroundHandler) {
         mSysUiContext = context;
         final Display display = mSysUiContext.getDisplay();
         mDisplayContext = display.getDisplayId() == DEFAULT_DISPLAY
                 ? mSysUiContext : mSysUiContext.createDisplayContext(display);
         mCommandQueue = commandQueue;
         mSecureSettings = secureSettings;
+        mLazyViewCapture = toKotlinLazy(daggerLazyViewCapture);
+        mBackgroundHandler = backgroundHandler;
     }
 
     boolean loadSetting(int currentUserId) {
@@ -239,14 +254,14 @@
     private void handleHide() {
         if (mClingWindow != null) {
             if (DEBUG) Log.d(TAG, "Hiding immersive mode confirmation");
-            if (mWindowManager != null) {
+            if (mViewCaptureAwareWindowManager != null) {
                 try {
-                    mWindowManager.removeView(mClingWindow);
+                    mViewCaptureAwareWindowManager.removeView(mClingWindow);
                 } catch (WindowManager.InvalidDisplayException e) {
                     Log.w(TAG, "Fail to hide the immersive confirmation window because of "
                             + e);
                 }
-                mWindowManager = null;
+                mViewCaptureAwareWindowManager = null;
                 mWindowContext = null;
             }
             mClingWindow = null;
@@ -318,7 +333,7 @@
                 }
             }
             TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
-            mContentObserver = new ContentObserver(mHandler) {
+            mContentObserver = new ContentObserver(mBackgroundHandler) {
                 @Override
                 public void onChange(boolean selfChange) {
                     onSettingChanged(mSysUiContext.getUserId());
@@ -332,6 +347,9 @@
             mSecureSettings.registerContentObserverForUserSync(
                     Settings.Secure.USER_SETUP_COMPLETE, mContentObserver,
                     UserHandle.USER_CURRENT);
+            mBackgroundHandler.post(() -> {
+                loadSetting(UserHandle.USER_CURRENT);
+            });
         }
     }
 
@@ -505,8 +523,8 @@
      *         confirmation window.
      */
     @NonNull
-    private WindowManager createWindowManager(int rootDisplayAreaId) {
-        if (mWindowManager != null) {
+    private ViewCaptureAwareWindowManager createWindowManager(int rootDisplayAreaId) {
+        if (mViewCaptureAwareWindowManager != null) {
             throw new IllegalStateException(
                     "Must not create a new WindowManager while there is an existing one");
         }
@@ -515,8 +533,10 @@
         mWindowContextRootDisplayAreaId = rootDisplayAreaId;
         mWindowContext = mDisplayContext.createWindowContext(
                 IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options);
-        mWindowManager = mWindowContext.getSystemService(WindowManager.class);
-        return mWindowManager;
+        WindowManager wm = mWindowContext.getSystemService(WindowManager.class);
+        mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(wm, mLazyViewCapture,
+                enableViewCaptureTracing());
+        return mViewCaptureAwareWindowManager;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 696e222..d523bc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -255,8 +255,8 @@
         }
 
         final float stackBottom = SceneContainerFlag.isEnabled()
-                ? ambientState.getStackTop() + ambientState.getStackHeight()
-                : ambientState.getStackY() + ambientState.getStackHeight();
+                ? ambientState.getStackTop() + ambientState.getInterpolatedStackHeight()
+                : ambientState.getStackY() + ambientState.getInterpolatedStackHeight();
 
         if (viewState.hidden) {
             // if the shelf is hidden, position it at the end of the stack (plus the clip
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 3068460..87f360e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -58,6 +58,7 @@
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarIcon.Shape;
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.Flags;
 import com.android.systemui.res.R;
@@ -211,16 +212,19 @@
     /** Should always be preceded by {@link #reloadDimens()} */
     @VisibleForTesting
     public void maybeUpdateIconScaleDimens() {
-        // We do not resize and scale system icons (on the right), only notification icons (on the
-        // left).
-        if (isNotification()) {
-            updateIconScaleForNotifications();
+        // We scale notification icons (on the left) plus icons on the right that explicitly
+        // want FIXED_SPACE.
+        boolean useNonSystemIconScaling = isNotification()
+                || (usesModeIcons() && mIcon != null && mIcon.shape == Shape.FIXED_SPACE);
+
+        if (useNonSystemIconScaling) {
+            updateIconScaleForNonSystemIcons();
         } else {
             updateIconScaleForSystemIcons();
         }
     }
 
-    private void updateIconScaleForNotifications() {
+    private void updateIconScaleForNonSystemIcons() {
         float iconScale;
         // we need to scale the image size to be same as the original size
         // (fit mOriginalStatusBarIconSize), then we can scale it with mScaleToFitNewIconSize
@@ -411,7 +415,9 @@
         if (!levelEquals) {
             setImageLevel(icon.iconLevel);
         }
-
+        if (usesModeIcons() && icon.shape == Shape.FIXED_SPACE) {
+            setScaleType(ScaleType.FIT_CENTER);
+        }
         if (!visibilityEquals) {
             setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
         }
@@ -471,17 +477,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 +505,31 @@
     }
 
     @Nullable
+    private Drawable loadDrawable(Context context, StatusBarIcon statusBarIcon) {
+        if (usesModeIcons() && statusBarIcon.preloadedIcon != null) {
+            Drawable.ConstantState cached = statusBarIcon.preloadedIcon.getConstantState();
+            if (cached != null) {
+                return cached.newDrawable(mContext.getResources()).mutate();
+            } else {
+                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 +1041,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..cce9a16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt
@@ -0,0 +1,174 @@
+/*
+ * 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 and color:
+ * ```
+ * adb shell cmd statusbar demo-ron -p com.android.systemui -t 10min -c "\\#434343"
+ * ```
+ *
+ * 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 backgroundColor: Int? by
+            param(
+                longName = "color",
+                shortName = "c",
+                description =
+                    "The color to show as the chip background color. " +
+                        "You can either just write a basic color like 'red' or 'green', " +
+                        "or you can include a #RRGGBB string in this format: \"\\\\#434343\".",
+                valueParser = Type.Color,
+            )
+
+        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 colors =
+                if (backgroundColor != null) {
+                    ColorsModel.Custom(backgroundColorInt = backgroundColor!!)
+                } else {
+                    ColorsModel.Themed
+                }
+
+            val currentText = text
+            if (currentText != null) {
+                _chip.value =
+                    OngoingActivityChipModel.Shown.Text(
+                        icon = appIcon,
+                        colors = colors,
+                        text = currentText,
+                    )
+            } else {
+                _chip.value =
+                    OngoingActivityChipModel.Shown.Timer(
+                        icon = appIcon,
+                        colors = colors,
+                        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/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
new file mode 100644
index 0000000..3b1e565
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -0,0 +1,314 @@
+/*
+ * 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.ui.binder
+
+import android.annotation.IdRes
+import android.content.res.ColorStateList
+import android.graphics.drawable.GradientDrawable
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips
+import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+
+/** Binder for ongoing activity chip views. */
+object OngoingActivityChipBinder {
+    /** Binds the given [chipModel] data to the given [chipView]. */
+    fun bind(chipModel: OngoingActivityChipModel, chipView: View) {
+        val chipContext = chipView.context
+        val chipDefaultIconView: ImageView =
+            chipView.requireViewById(R.id.ongoing_activity_chip_icon)
+        val chipTimeView: ChipChronometer =
+            chipView.requireViewById(R.id.ongoing_activity_chip_time)
+        val chipTextView: TextView = chipView.requireViewById(R.id.ongoing_activity_chip_text)
+        val chipBackgroundView: ChipBackgroundContainer =
+            chipView.requireViewById(R.id.ongoing_activity_chip_background)
+
+        when (chipModel) {
+            is OngoingActivityChipModel.Shown -> {
+                // Data
+                setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView)
+                setChipMainContent(chipModel, chipTextView, chipTimeView)
+                chipView.setOnClickListener(chipModel.onClickListener)
+                updateChipPadding(
+                    chipModel,
+                    chipBackgroundView,
+                    chipTextView,
+                    chipTimeView,
+                )
+
+                // Accessibility
+                setChipAccessibility(chipModel, chipView, chipBackgroundView)
+
+                // Colors
+                val textColor = chipModel.colors.text(chipContext)
+                chipTimeView.setTextColor(textColor)
+                chipTextView.setTextColor(textColor)
+                (chipBackgroundView.background as GradientDrawable).color =
+                    chipModel.colors.background(chipContext)
+            }
+            is OngoingActivityChipModel.Hidden -> {
+                // The Chronometer should be stopped to prevent leaks -- see b/192243808 and
+                // [Chronometer.start].
+                chipTimeView.stop()
+            }
+        }
+    }
+
+    private fun setChipIcon(
+        chipModel: OngoingActivityChipModel.Shown,
+        backgroundView: ChipBackgroundContainer,
+        defaultIconView: ImageView,
+    ) {
+        // Always remove any previously set custom icon. If we have a new custom icon, we'll re-add
+        // 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.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.
+                defaultIconView.visibility = View.GONE
+
+                // Add the new custom icon:
+                // 1. Set up the right visual params.
+                val iconView = icon.impl
+                with(iconView) {
+                    id = CUSTOM_ICON_VIEW_ID
+                    // TODO(b/354930838): Update the content description to not include "phone" and
+                    // 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
+                // old chip before we reattach it to the new one.
+                // See also: NotificationIconContainerViewBinder#bindIcons.
+                val currentParent = iconView.parent as? ViewGroup
+                if (currentParent != null && currentParent != backgroundView) {
+                    currentParent.removeView(iconView)
+                    currentParent.removeTransientView(iconView)
+                }
+
+                // 3: Add the icon as the starting view.
+                backgroundView.addView(
+                    iconView,
+                    /* index= */ 0,
+                    generateCustomIconLayoutParams(iconView),
+                )
+            }
+        }
+    }
+
+    private fun View.getCustomIconView(): StatusBarIconView? {
+        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(
+                R.dimen.ongoing_activity_chip_embedded_padding_icon_size
+            )
+        return FrameLayout.LayoutParams(customIconSize, customIconSize)
+    }
+
+    private fun setChipMainContent(
+        chipModel: OngoingActivityChipModel.Shown,
+        chipTextView: TextView,
+        chipTimeView: ChipChronometer,
+    ) {
+        when (chipModel) {
+            is OngoingActivityChipModel.Shown.Countdown -> {
+                chipTextView.text = chipModel.secondsUntilStarted.toString()
+                chipTextView.visibility = View.VISIBLE
+
+                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)
+                chipTimeView.visibility = View.VISIBLE
+
+                chipTextView.visibility = View.GONE
+            }
+            is OngoingActivityChipModel.Shown.IconOnly -> {
+                chipTextView.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,
+        chipTextView: TextView,
+        chipTimeView: ChipChronometer,
+    ) {
+        if (chipModel.icon != null) {
+            if (chipModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarView) {
+                // If the icon is a custom [StatusBarIconView], then it should've come from
+                // `Notification.smallIcon`, which is required to embed its own paddings. We need to
+                // adjust the other paddings to make everything look good :)
+                backgroundView.setBackgroundPaddingForEmbeddedPaddingIcon()
+                chipTextView.setTextPaddingForEmbeddedPaddingIcon()
+                chipTimeView.setTextPaddingForEmbeddedPaddingIcon()
+            } else {
+                backgroundView.setBackgroundPaddingForNormalIcon()
+                chipTextView.setTextPaddingForNormalIcon()
+                chipTimeView.setTextPaddingForNormalIcon()
+            }
+        } else {
+            backgroundView.setBackgroundPaddingForNoIcon()
+            chipTextView.setTextPaddingForNoIcon()
+            chipTimeView.setTextPaddingForNoIcon()
+        }
+    }
+
+    private fun View.setTextPaddingForEmbeddedPaddingIcon() {
+        val newPaddingEnd =
+            context.resources.getDimensionPixelSize(
+                R.dimen.ongoing_activity_chip_text_end_padding_for_embedded_padding_icon
+            )
+        setPaddingRelative(
+            // The icon should embed enough padding between the icon and time view.
+            /* start= */ 0,
+            this.paddingTop,
+            newPaddingEnd,
+            this.paddingBottom,
+        )
+    }
+
+    private fun View.setTextPaddingForNormalIcon() {
+        this.setPaddingRelative(
+            this.context.resources.getDimensionPixelSize(
+                R.dimen.ongoing_activity_chip_icon_text_padding
+            ),
+            paddingTop,
+            // The background view will contain the right end padding.
+            /* end= */ 0,
+            paddingBottom,
+        )
+    }
+
+    private fun View.setTextPaddingForNoIcon() {
+        // The background view will have even start & end paddings, so we don't want the text view
+        // to add any additional padding.
+        this.setPaddingRelative(/* start= */ 0, paddingTop, /* end= */ 0, paddingBottom)
+    }
+
+    private fun View.setBackgroundPaddingForEmbeddedPaddingIcon() {
+        val sidePadding =
+            context.resources.getDimensionPixelSize(
+                R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon
+            )
+        setPaddingRelative(
+            sidePadding,
+            paddingTop,
+            sidePadding,
+            paddingBottom,
+        )
+    }
+
+    private fun View.setBackgroundPaddingForNormalIcon() {
+        val sidePadding =
+            context.resources.getDimensionPixelSize(R.dimen.ongoing_activity_chip_side_padding)
+        setPaddingRelative(
+            sidePadding,
+            paddingTop,
+            sidePadding,
+            paddingBottom,
+        )
+    }
+
+    private fun View.setBackgroundPaddingForNoIcon() {
+        // The padding for the normal icon is also appropriate for no icon.
+        setBackgroundPaddingForNormalIcon()
+    }
+
+    private fun setChipAccessibility(
+        chipModel: OngoingActivityChipModel.Shown,
+        chipView: View,
+        chipBackgroundView: View,
+    ) {
+        when (chipModel) {
+            is OngoingActivityChipModel.Shown.Countdown -> {
+                // Set as assertive so talkback will announce the countdown
+                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
+            }
+        }
+        // Clickable chips need to be a minimum size for accessibility purposes, but let
+        // non-clickable chips be smaller.
+        if (chipModel.onClickListener != null) {
+            chipBackgroundView.minimumWidth =
+                chipBackgroundView.context.resources.getDimensionPixelSize(
+                    R.dimen.min_clickable_item_size
+                )
+        } else {
+            chipBackgroundView.minimumWidth = 0
+        }
+    }
+
+    @IdRes private val CUSTOM_ICON_VIEW_ID = R.id.ongoing_activity_chip_custom_icon
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
index 8a5165d8..4b0fc5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt
@@ -39,6 +39,24 @@
             Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
     }
 
+    /**
+     * The chip should have the given background color, and text color that matches dark/light
+     * theme.
+     */
+    data class Custom(val backgroundColorInt: Int) : ColorsModel {
+        override fun background(context: Context): ColorStateList =
+            ColorStateList.valueOf(backgroundColorInt)
+
+        // TODO(b/361346412): When dark theme changes, the chip should automatically re-render with
+        // the right text color. Right now, it has the right text color when the chip is first
+        // created but the color doesn't update if dark theme changes.
+        override fun text(context: Context) =
+            Utils.getColorAttrDefaultColor(
+                context,
+                com.android.internal.R.attr.materialColorOnSurface,
+            )
+    }
+
     /** The chip should have a red background with white text. */
     data object Red : ColorsModel {
         override fun background(context: Context): ColorStateList {
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..199eb06 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,30 +128,36 @@
                     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,
                     )
                 }
             }
         }
 
     /**
-     * A flow modeling the chip that should be shown in the status bar after accounting for possibly
-     * multiple ongoing activities and animation requirements.
+     * A flow modeling the primary chip that should be shown in the status bar after accounting for
+     * possibly multiple ongoing activities and animation requirements.
      *
      * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment] is responsible for
      * actually displaying the chip.
      */
-    val chip: StateFlow<OngoingActivityChipModel> =
+    val primaryChip: StateFlow<OngoingActivityChipModel> =
         internalChip
             .pairwise(initialValue = DEFAULT_INTERNAL_HIDDEN_MODEL)
             .map { (old, new) ->
@@ -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/commandline/ValueParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
index 01083d9..412c8c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.commandline
 
+import androidx.core.graphics.toColorInt
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
@@ -164,10 +165,23 @@
         ?: Result.failure(ArgParseError("Failed to parse $value as a float"))
 }
 
+// See https://developer.android.com/reference/android/graphics/Color#parseColor(java.lang.String)
+// for the supported formats of the color string. tl;dr: #RRGGBB, #AARRGGBB, or a basic color name
+// like "red" or "green". For the RRGGBB values, the `#` needs to be escaped. Use `"\\#RRGGBB"` in
+// the command to escape the `#` correctly.
+private val parseColor: ValueParser<Int> = ValueParser { value ->
+    try {
+        Result.success(value.toColorInt())
+    } catch (e: IllegalArgumentException) {
+        Result.failure(ArgParseError("Failed to parse $value as a color: $e"))
+    }
+}
+
 /** Default parsers that can be use as-is, or [map]ped to another type */
 object Type {
     val Boolean = parseBoolean
     val Int = parseInt
     val Float = parseFloat
     val String = parseString
+    val Color = parseColor
 }
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/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index f74c9a6..e9292f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -79,6 +79,7 @@
             // NOTE: NotificationEntry.isClearable will internally check group children to ensure
             //  the group itself definitively clearable.
             val isClearable = !isSensitiveContentProtectionActive && entry.isClearable
+                    && !entry.isSensitive.value
             when {
                 isSilent && isClearable -> hasClearableSilentNotifs = true
                 isSilent && !isClearable -> hasNonClearableSilentNotifs = true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 3a2f95e..6d0148a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -22,7 +22,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.Dumpable;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
@@ -32,6 +32,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
@@ -43,6 +44,7 @@
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.BooleanFlowOperators;
 import com.android.systemui.util.kotlin.JavaAdapter;
 
 import java.io.PrintWriter;
@@ -70,7 +72,8 @@
     private final VisibilityLocationProvider mVisibilityLocationProvider;
     private final VisualStabilityProvider mVisualStabilityProvider;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final CommunalInteractor mCommunalInteractor;
+    private final CommunalSceneInteractor mCommunalSceneInteractor;
+    private final ShadeInteractor mShadeInteractor;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final VisualStabilityCoordinatorLogger mLogger;
 
@@ -110,7 +113,8 @@
             VisibilityLocationProvider visibilityLocationProvider,
             VisualStabilityProvider visualStabilityProvider,
             WakefulnessLifecycle wakefulnessLifecycle,
-            CommunalInteractor communalInteractor,
+            CommunalSceneInteractor communalSceneInteractor,
+            ShadeInteractor shadeInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             VisualStabilityCoordinatorLogger logger) {
         mHeadsUpManager = headsUpManager;
@@ -122,7 +126,8 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mStatusBarStateController = statusBarStateController;
         mDelayableExecutor = delayableExecutor;
-        mCommunalInteractor = communalInteractor;
+        mCommunalSceneInteractor = communalSceneInteractor;
+        mShadeInteractor = shadeInteractor;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mLogger = logger;
 
@@ -141,7 +146,11 @@
                 this::onShadeOrQsClosingChanged);
         mJavaAdapter.alwaysCollectFlow(mShadeAnimationInteractor.isLaunchingActivity(),
                 this::onLaunchingActivityChanged);
-        mJavaAdapter.alwaysCollectFlow(mCommunalInteractor.isIdleOnCommunal(),
+        mJavaAdapter.alwaysCollectFlow(
+                BooleanFlowOperators.INSTANCE.allOf(
+                        mCommunalSceneInteractor.isIdleOnCommunal(),
+                        BooleanFlowOperators.INSTANCE.not(mShadeInteractor.isAnyFullyExpanded())
+                ),
                 this::onCommunalShowingChanged);
 
         if (SceneContainerFlag.isEnabled()) {
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/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 17f401a..0efd5f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -45,7 +45,6 @@
 import android.service.notification.Flags
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -279,7 +278,8 @@
     private val packageManager: PackageManager,
     private val uiEventLogger: UiEventLogger,
     private val context: Context,
-    private val notificationManager: NotificationManager
+    private val notificationManager: NotificationManager,
+    private val logger: VisualInterruptionDecisionLogger
 ) :
     VisualInterruptionFilter(
         types = setOf(PEEK, PULSE),
@@ -354,15 +354,18 @@
 
     override fun shouldSuppress(entry: NotificationEntry): Boolean {
         if (!isCooldownEnabled()) {
+            logger.logAvalancheAllow("cooldown OFF")
             return false
         }
         val timeSinceAvalancheMs = systemClock.currentTimeMillis() - avalancheProvider.startTime
         val timedOut = timeSinceAvalancheMs >= avalancheProvider.timeoutMs
         if (timedOut) {
+            logger.logAvalancheAllow("timedOut! timeSinceAvalancheMs=$timeSinceAvalancheMs")
             return false
         }
         val state = calculateState(entry)
         if (state != State.SUPPRESS) {
+            logger.logAvalancheAllow("state=$state")
             return false
         }
         if (shouldShowEdu()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
index c204ea9..b83259d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
@@ -93,6 +93,15 @@
             }
         )
     }
+
+    fun logAvalancheAllow(info: String) {
+        buffer.log(
+            TAG,
+            INFO,
+            { str1 = info },
+            { "AvalancheSuppressor: $str1" }
+        )
+    }
 }
 
 private const val TAG = "VisualInterruptionDecisionProvider"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 8e8d9b6..2f8711a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -194,7 +194,8 @@
                     packageManager,
                     uiEventLogger,
                     context,
-                    notificationManager
+                    notificationManager,
+                    logger
                 )
             )
             avalancheProvider.register()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto
index ce4356a..18d4a04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto
@@ -18,7 +18,7 @@
 
 /**
  * NotificationList proto from atoms.proto, duplicated here so that it's accessible in the build.
- * Must be kept in sync with the version in atoms.proto.
+ * Must be kept in sync with the version in stats/atoms/sysui/sysui_atoms.proto.
  */
 
 message Notification {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 7119145..48c974a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -117,6 +117,8 @@
     protected HybridNotificationView mSingleLineView;
 
     @Nullable public DisposableHandle mContractedBinderHandle;
+    @Nullable public DisposableHandle mExpandedBinderHandle;
+    @Nullable public DisposableHandle mHeadsUpBinderHandle;
 
     private RemoteInputView mExpandedRemoteInput;
     private RemoteInputView mHeadsUpRemoteInput;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index a5cd2a2..c342bcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -46,6 +46,9 @@
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
 import com.android.systemui.statusbar.notification.InflationException
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+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.NotificationContentView.VISIBLE_TYPE_CONTRACTED
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -286,11 +289,15 @@
                 }
             FLAG_CONTENT_VIEW_EXPANDED ->
                 row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_EXPANDED) {
+                    row.privateLayout.mExpandedBinderHandle?.dispose()
+                    row.privateLayout.mExpandedBinderHandle = null
                     row.privateLayout.setExpandedChild(null)
                     remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
                 }
             FLAG_CONTENT_VIEW_HEADS_UP ->
                 row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_HEADSUP) {
+                    row.privateLayout.mHeadsUpBinderHandle?.dispose()
+                    row.privateLayout.mHeadsUpBinderHandle = null
                     row.privateLayout.setHeadsUpChild(null)
                     remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
                     row.privateLayout.setHeadsUpInflatedSmartReplies(null)
@@ -499,17 +506,87 @@
                     }
             }
 
-            if (reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0) {
+            val richOngoingContentModel = inflationProgress.contentModel.richOngoingContentModel
+
+            if (
+                richOngoingContentModel != null &&
+                    reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
+            ) {
                 logger.logAsyncTaskProgress(entry, "inflating RON view")
-                inflationProgress.richOngoingNotificationViewHolder =
-                    inflationProgress.contentModel.richOngoingContentModel?.let {
+                val inflateContractedView = reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0
+                val inflateExpandedView = reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0
+                val inflateHeadsUpView = reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0
+
+                inflationProgress.contractedRichOngoingNotificationViewHolder =
+                    if (inflateContractedView) {
                         ronInflater.inflateView(
-                            contentModel = it,
+                            contentModel = richOngoingContentModel,
                             existingView = row.privateLayout.contractedChild,
                             entry = entry,
                             systemUiContext = context,
-                            parentView = row.privateLayout
+                            parentView = row.privateLayout,
+                            viewType = RichOngoingNotificationViewType.Contracted
                         )
+                    } else {
+                        if (
+                            ronInflater.canKeepView(
+                                contentModel = richOngoingContentModel,
+                                existingView = row.privateLayout.contractedChild,
+                                viewType = RichOngoingNotificationViewType.Contracted
+                            )
+                        ) {
+                            KeepExistingView
+                        } else {
+                            NullContentView
+                        }
+                    }
+
+                inflationProgress.expandedRichOngoingNotificationViewHolder =
+                    if (inflateExpandedView) {
+                        ronInflater.inflateView(
+                            contentModel = richOngoingContentModel,
+                            existingView = row.privateLayout.expandedChild,
+                            entry = entry,
+                            systemUiContext = context,
+                            parentView = row.privateLayout,
+                            viewType = RichOngoingNotificationViewType.Expanded
+                        )
+                    } else {
+                        if (
+                            ronInflater.canKeepView(
+                                contentModel = richOngoingContentModel,
+                                existingView = row.privateLayout.expandedChild,
+                                viewType = RichOngoingNotificationViewType.Expanded
+                            )
+                        ) {
+                            KeepExistingView
+                        } else {
+                            NullContentView
+                        }
+                    }
+
+                inflationProgress.headsUpRichOngoingNotificationViewHolder =
+                    if (inflateHeadsUpView) {
+                        ronInflater.inflateView(
+                            contentModel = richOngoingContentModel,
+                            existingView = row.privateLayout.headsUpChild,
+                            entry = entry,
+                            systemUiContext = context,
+                            parentView = row.privateLayout,
+                            viewType = RichOngoingNotificationViewType.HeadsUp
+                        )
+                    } else {
+                        if (
+                            ronInflater.canKeepView(
+                                contentModel = richOngoingContentModel,
+                                existingView = row.privateLayout.headsUpChild,
+                                viewType = RichOngoingNotificationViewType.HeadsUp
+                            )
+                        ) {
+                            KeepExistingView
+                        } else {
+                            NullContentView
+                        }
                     }
             }
 
@@ -618,7 +695,9 @@
         var inflatedSmartReplyState: InflatedSmartReplyState? = null
         var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
         var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
-        var richOngoingNotificationViewHolder: InflatedContentViewHolder? = null
+        var contractedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
+        var expandedRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
+        var headsUpRichOngoingNotificationViewHolder: ContentViewInflationResult? = null
 
         // Inflated SingleLineView that lacks the UI State
         var inflatedSingleLineView: HybridNotificationView? = null
@@ -1428,14 +1507,21 @@
             logger.logAsyncTaskProgress(entry, "finishing")
 
             // before updating the content model, stop existing binding if necessary
-            val hasRichOngoingContentModel = result.contentModel.richOngoingContentModel != null
-            val requestedRichOngoing = reInflateFlags and CONTENT_VIEWS_TO_CREATE_RICH_ONGOING != 0
-            val rejectedRichOngoing = requestedRichOngoing && !hasRichOngoingContentModel
-            if (result.richOngoingNotificationViewHolder != null || rejectedRichOngoing) {
+            if (result.contractedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
                 row.privateLayout.mContractedBinderHandle?.dispose()
                 row.privateLayout.mContractedBinderHandle = null
             }
 
+            if (result.expandedRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
+                row.privateLayout.mExpandedBinderHandle?.dispose()
+                row.privateLayout.mExpandedBinderHandle = null
+            }
+
+            if (result.headsUpRichOngoingNotificationViewHolder.shouldDisposeViewBinder()) {
+                row.privateLayout.mHeadsUpBinderHandle?.dispose()
+                row.privateLayout.mHeadsUpBinderHandle = null
+            }
+
             // set the content model after disposal and before setting new rich ongoing view
             entry.setContentModel(result.contentModel)
             result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
@@ -1477,19 +1563,53 @@
                 }
             }
 
-            // after updating the content model, set the view, then start the new binder
-            result.richOngoingNotificationViewHolder?.let { viewHolder ->
-                row.privateLayout.contractedChild = viewHolder.view
-                row.privateLayout.expandedChild = null
-                row.privateLayout.headsUpChild = null
-                row.privateLayout.setExpandedInflatedSmartReplies(null)
-                row.privateLayout.setHeadsUpInflatedSmartReplies(null)
-                row.privateLayout.mContractedBinderHandle =
-                    viewHolder.binder.setupContentViewBinder()
-                row.setExpandable(false)
+            val hasRichOngoingViewHolder =
+                result.contractedRichOngoingNotificationViewHolder != null ||
+                    result.expandedRichOngoingNotificationViewHolder != null ||
+                    result.headsUpRichOngoingNotificationViewHolder != null
+
+            if (hasRichOngoingViewHolder) {
+                // after updating the content model, set the view, then start the new binder
+                result.contractedRichOngoingNotificationViewHolder?.let { contractedViewHolder ->
+                    if (contractedViewHolder is InflatedContentViewHolder) {
+                        row.privateLayout.contractedChild = contractedViewHolder.view
+                        row.privateLayout.mContractedBinderHandle =
+                            contractedViewHolder.binder.setupContentViewBinder()
+                    } else if (contractedViewHolder == NullContentView) {
+                        row.privateLayout.contractedChild = null
+                    }
+                }
+
+                result.expandedRichOngoingNotificationViewHolder?.let { expandedViewHolder ->
+                    if (expandedViewHolder is InflatedContentViewHolder) {
+                        row.privateLayout.expandedChild = expandedViewHolder.view
+                        row.privateLayout.mExpandedBinderHandle =
+                            expandedViewHolder.binder.setupContentViewBinder()
+                    } else if (expandedViewHolder == NullContentView) {
+                        row.privateLayout.expandedChild = null
+                    }
+                }
+
+                result.headsUpRichOngoingNotificationViewHolder?.let { headsUpViewHolder ->
+                    if (headsUpViewHolder is InflatedContentViewHolder) {
+                        row.privateLayout.headsUpChild = headsUpViewHolder.view
+                        row.privateLayout.mHeadsUpBinderHandle =
+                            headsUpViewHolder.binder.setupContentViewBinder()
+                    } else if (headsUpViewHolder == NullContentView) {
+                        row.privateLayout.headsUpChild = null
+                    }
+                }
+
+                // clean remoteViewCache when we don't keep existing views.
                 remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
                 remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
                 remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+
+                // Since RONs don't support smart reply, remove them from HUNs and Expanded.
+                row.privateLayout.setExpandedInflatedSmartReplies(null)
+                row.privateLayout.setHeadsUpInflatedSmartReplies(null)
+
+                row.setExpandable(row.privateLayout.expandedChild != null)
             }
 
             Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
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 e9c4960..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
@@ -24,12 +24,18 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+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
@@ -39,7 +45,35 @@
     fun setupContentViewBinder(): DisposableHandle
 }
 
-class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder)
+enum class RichOngoingNotificationViewType {
+    Contracted,
+    Expanded,
+    HeadsUp,
+}
+
+/**
+ * * Supertype of the 3 different possible result types of
+ *   [RichOngoingNotificationViewInflater.inflateView].
+ */
+sealed interface ContentViewInflationResult {
+
+    /** Indicates that the content view should be removed if present. */
+    data object NullContentView : ContentViewInflationResult
+
+    /**
+     * Indicates that the content view (which *must be* present) should be unmodified during this
+     * inflation.
+     */
+    data object KeepExistingView : ContentViewInflationResult
+
+    /**
+     * Contains the new view and binder that should replace any existing content view for this slot.
+     */
+    data class InflatedContentViewHolder(val view: View, val binder: DeferredContentViewBinder) :
+        ContentViewInflationResult
+}
+
+fun ContentViewInflationResult?.shouldDisposeViewBinder() = this !is KeepExistingView
 
 /**
  * Interface which provides a [RichOngoingContentModel] for a given [Notification] when one is
@@ -52,7 +86,14 @@
         entry: NotificationEntry,
         systemUiContext: Context,
         parentView: ViewGroup,
-    ): InflatedContentViewHolder?
+        viewType: RichOngoingNotificationViewType,
+    ): ContentViewInflationResult
+
+    fun canKeepView(
+        contentModel: RichOngoingContentModel,
+        existingView: View?,
+        viewType: RichOngoingNotificationViewType
+    ): Boolean
 }
 
 @SysUISingleton
@@ -68,8 +109,9 @@
         entry: NotificationEntry,
         systemUiContext: Context,
         parentView: ViewGroup,
-    ): InflatedContentViewHolder? {
-        if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return null
+        viewType: RichOngoingNotificationViewType,
+    ): ContentViewInflationResult {
+        if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return NullContentView
         val component = viewModelComponentFactory.create(entry)
         return when (contentModel) {
             is TimerContentModel ->
@@ -77,9 +119,31 @@
                     existingView,
                     component::createTimerViewModel,
                     systemUiContext,
-                    parentView
+                    parentView,
+                    viewType
                 )
-            is StopwatchContentModel -> TODO("Not yet implemented")
+            is EnRouteContentModel ->
+                inflateEnRouteView(
+                    existingView,
+                    component::createEnRouteViewModel,
+                    systemUiContext,
+                    parentView,
+                    viewType
+                )
+            else -> TODO("Not yet implemented")
+        }
+    }
+
+    override fun canKeepView(
+        contentModel: RichOngoingContentModel,
+        existingView: View?,
+        viewType: RichOngoingNotificationViewType
+    ): Boolean {
+        if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false
+        return when (contentModel) {
+            is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType)
+            is EnRouteContentModel -> canKeepEnRouteView(contentModel, existingView, viewType)
+            else -> TODO("Not yet implemented")
         }
     }
 
@@ -88,17 +152,65 @@
         createViewModel: () -> TimerViewModel,
         systemUiContext: Context,
         parentView: ViewGroup,
-    ): InflatedContentViewHolder? {
-        if (existingView is TimerView && !existingView.isReinflateNeeded()) return null
-        val newView =
-            LayoutInflater.from(systemUiContext)
-                .inflate(
-                    R.layout.rich_ongoing_timer_notification,
-                    parentView,
-                    /* attachToRoot= */ false
-                ) as TimerView
-        return InflatedContentViewHolder(newView) {
-            TimerViewBinder.bindWhileAttached(newView, createViewModel())
+        viewType: RichOngoingNotificationViewType,
+    ): ContentViewInflationResult {
+        if (existingView is TimerView && !existingView.isReinflateNeeded()) return KeepExistingView
+
+        return when (viewType) {
+            RichOngoingNotificationViewType.Contracted -> {
+                val newView =
+                    LayoutInflater.from(systemUiContext)
+                        .inflate(
+                            R.layout.rich_ongoing_timer_notification,
+                            parentView,
+                            /* attachToRoot= */ false
+                        ) as TimerView
+                InflatedContentViewHolder(newView) {
+                    TimerViewBinder.bindWhileAttached(newView, createViewModel())
+                }
+            }
+            RichOngoingNotificationViewType.Expanded,
+            RichOngoingNotificationViewType.HeadsUp -> NullContentView
         }
     }
+
+    private fun canKeepTimerView(
+        contentModel: TimerContentModel,
+        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 7c3072d..1431b28 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;
 
     /**
@@ -139,6 +138,9 @@
     /** Fraction of shade expansion. */
     private float mExpansionFraction;
 
+    /** Fraction of QS expansion. 0 when in shade, 1 when in QS. */
+    private float mQsExpansionFraction;
+
     /** Height of the notifications panel when expansion completes. */
     private float mStackEndHeight;
 
@@ -171,7 +173,8 @@
     }
 
     /**
-     * @return Height of the notifications panel without top padding when expansion completes.
+     * @return Height of the available space for the notification content, when the shade
+     * expansion completes.
      */
     public float getStackEndHeight() {
         return mStackEndHeight;
@@ -208,6 +211,14 @@
     }
 
     /**
+     * @param expansionFraction Fraction of QS expansion.
+     */
+    public void setQsExpansionFraction(float expansionFraction) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mQsExpansionFraction = expansionFraction;
+    }
+
+    /**
      * @param isSwipingUp Whether we are swiping up.
      */
     public void setSwipingUp(boolean isSwipingUp) {
@@ -258,19 +269,28 @@
     }
 
     /**
-     * @see #getStackHeight()
+     * @return Fraction of QS expansion.
      */
-    public void setStackHeight(float stackHeight) {
-        mStackHeight = stackHeight;
+    public float getQsExpansionFraction() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+        return mQsExpansionFraction;
     }
 
     /**
-     * @return Height of notifications panel interpolated by the expansion fraction.
+     * @return Height of the notification content returned by {@link #getStackEndHeight()}, but
+     * interpolated by the shade expansion fraction.
      */
-    public float getStackHeight() {
+    public float getInterpolatedStackHeight() {
         return mStackHeight;
     }
 
+    /**
+     * @see #getInterpolatedStackHeight()
+     */
+    public void setInterpolatedStackHeight(float stackHeight) {
+        mStackHeight = stackHeight;
+    }
+
     @Inject
     public AmbientState(
             @NonNull Context context,
@@ -492,10 +512,12 @@
     }
 
     public int getTopPadding() {
+        SceneContainerFlag.assertInLegacyMode();
         return mTopPadding;
     }
 
     public void setTopPadding(int topPadding) {
+        SceneContainerFlag.assertInLegacyMode();
         mTopPadding = topPadding;
     }
 
@@ -511,8 +533,15 @@
         if (mDozeAmount == 1.0f && !isPulseExpanding()) {
             return mShelf.getHeight();
         }
-        int height = (int) Math.max(mLayoutMinHeight,
-                Math.min(mLayoutHeight, mContentHeight) - mTopPadding);
+        int height;
+        if (SceneContainerFlag.isEnabled()) {
+            // TODO(b/192348384): This is probably incorrect as mContentHeight is not up to date.
+            //  Consider removing usages of getInnerHeight in flexiglass if possible.
+            height = (int) Math.min(mLayoutHeight, mContentHeight) - mTopPadding;
+        } else {
+            height = (int) Math.max(mLayoutMinHeight,
+                    Math.min(mLayoutHeight, mContentHeight) - mTopPadding);
+        }
         if (ignorePulseHeight) {
             return height;
         }
@@ -549,6 +578,7 @@
     }
 
     public void setLayoutMinHeight(int layoutMinHeight) {
+        SceneContainerFlag.assertInLegacyMode();
         mLayoutMinHeight = layoutMinHeight;
     }
 
@@ -697,14 +727,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;
@@ -835,8 +857,8 @@
         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);
         pw.println("mOverScrollTopAmount=" + mOverScrollTopAmount);
         pw.println("mOverScrollBottomAmount=" + mOverScrollBottomAmount);
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 8f187f0..48e69893 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;
@@ -790,7 +789,6 @@
     private void onJustBeforeDraw() {
         if (SceneContainerFlag.isEnabled()) {
             if (mChildrenUpdateRequested) {
-                updateForcedScroll();
                 updateChildren();
                 mChildrenUpdateRequested = false;
             }
@@ -851,7 +849,7 @@
             return; // the rest of the fields are not important in Flexiglass
         }
 
-        y = getTopPadding();
+        y = mAmbientState.getTopPadding();
         drawDebugInfo(canvas, y, Color.RED, /* label= */ "getTopPadding() = " + y);
 
         y = getLayoutHeight();
@@ -875,7 +873,7 @@
         y = (int) (mAmbientState.getStackY());
         drawDebugInfo(canvas, y, Color.CYAN, /* label= */ "mAmbientState.getStackY() = " + y);
 
-        y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
+        y = (int) (mAmbientState.getStackY() + mAmbientState.getInterpolatedStackHeight());
         drawDebugInfo(canvas, y, Color.LTGRAY,
                 /* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight() = " + y);
 
@@ -1124,11 +1122,13 @@
 
     @Override
     public void addStackHeightChangedListener(@NonNull Runnable runnable) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mStackHeightChangedListeners.addIfAbsent(runnable);
     }
 
     @Override
     public void removeStackHeightChangedListener(@NonNull Runnable runnable) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
         mStackHeightChangedListeners.remove(runnable);
     }
 
@@ -1231,9 +1231,10 @@
 
     @Override
     public void setStackTop(float stackTop) {
-        mAmbientState.setStackTop(stackTop);
-        // TODO(b/332574413): replace the following with using stackTop
-        updateTopPadding(stackTop, isAddOrRemoveAnimationPending());
+        if (mAmbientState.getStackTop() != stackTop) {
+            mAmbientState.setStackTop(stackTop);
+            onTopPaddingChanged(/* animate = */ isAddOrRemoveAnimationPending());
+        }
     }
 
     @Override
@@ -1244,6 +1245,7 @@
     @Override
     public void setHeadsUpTop(float headsUpTop) {
         mAmbientState.setHeadsUpTop(headsUpTop);
+        requestChildrenUpdate();
     }
 
     @Override
@@ -1252,6 +1254,11 @@
     }
 
     @Override
+    public void closeGutsOnSceneTouch() {
+        mController.closeControlsDueToOutsideTouch();
+    }
+
+    @Override
     public void setSyntheticScrollConsumer(@Nullable Consumer<Float> consumer) {
         mScrollViewFields.setSyntheticScrollConsumer(consumer);
     }
@@ -1262,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);
     }
@@ -1296,8 +1308,10 @@
     }
 
     private void updateAlgorithmLayoutMinHeight() {
-        mAmbientState.setLayoutMinHeight(mQsFullScreen || isHeadsUpTransition()
-                ? getLayoutMinHeightInternal() : 0);
+        if (!SceneContainerFlag.isEnabled()) {
+            mAmbientState.setLayoutMinHeight(mQsFullScreen || isHeadsUpTransition()
+                    ? getLayoutMinHeightInternal() : 0);
+        }
     }
 
     /**
@@ -1385,28 +1399,30 @@
     }
 
     public int getTopPadding() {
-        return mAmbientState.getTopPadding();
+        // TODO(b/332574413) replace all usages of getTopPadding()
+        if (SceneContainerFlag.isEnabled()) {
+            return (int) mAmbientState.getStackTop();
+        } else {
+            return mAmbientState.getTopPadding();
+        }
     }
 
-    private void setTopPadding(int topPadding, boolean animate) {
-        if (getTopPadding() != topPadding) {
-            mAmbientState.setTopPadding(topPadding);
-            boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
-            updateAlgorithmHeightAndPadding();
-            updateContentHeight();
-            if (mAmbientState.isOnKeyguard()
-                    && !mShouldUseSplitNotificationShade
-                    && mShouldSkipTopPaddingAnimationAfterFold) {
-                mShouldSkipTopPaddingAnimationAfterFold = false;
-            } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
-                mTopPaddingNeedsAnimation = true;
-                mNeedsAnimation = true;
-            }
-            updateStackPosition();
-            requestChildrenUpdate();
-            notifyHeightChangeListener(null, shouldAnimate);
-            mAnimateNextTopPaddingChange = false;
+    private void onTopPaddingChanged(boolean animate) {
+        boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
+        updateAlgorithmHeightAndPadding();
+        updateContentHeight();
+        if (mAmbientState.isOnKeyguard()
+                && !mShouldUseSplitNotificationShade
+                && mShouldSkipTopPaddingAnimationAfterFold) {
+            mShouldSkipTopPaddingAnimationAfterFold = false;
+        } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
+            mTopPaddingNeedsAnimation = true;
+            mNeedsAnimation = true;
         }
+        updateStackPosition();
+        requestChildrenUpdate();
+        notifyHeightChangeListener(null, shouldAnimate);
+        mAnimateNextTopPaddingChange = false;
     }
 
     /**
@@ -1434,6 +1450,11 @@
      * @param listenerNeedsAnimation does the listener need to animate?
      */
     private void updateStackPosition(boolean listenerNeedsAnimation) {
+        // When scene container is active, we only want to recalculate stack heights.
+        if (SceneContainerFlag.isEnabled()) {
+            updateStackEndHeightAndStackHeight(mAmbientState.getExpansionFraction());
+            return;
+        }
         float topOverscrollAmount = mShouldUseSplitNotificationShade
                 ? getCurrentOverScrollAmount(true /* top */) : 0f;
         final float endTopPosition = getTopPadding() + mExtraTopInsetForFullShadeTransition
@@ -1446,10 +1467,8 @@
         if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
             fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
         }
-        if (!SceneContainerFlag.isEnabled()) {
-            final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
-            mAmbientState.setStackY(stackY);
-        }
+        final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
+        mAmbientState.setStackY(stackY);
 
         if (mOnStackYChanged != null) {
             mOnStackYChanged.accept(listenerNeedsAnimation);
@@ -1459,7 +1478,7 @@
 
     @VisibleForTesting
     public void updateStackEndHeightAndStackHeight(float fraction) {
-        final float oldStackHeight = mAmbientState.getStackHeight();
+        final float oldStackHeight = mAmbientState.getInterpolatedStackHeight();
         if (SceneContainerFlag.isEnabled()) {
             final float endHeight;
             if (!shouldSkipHeightUpdate()) {
@@ -1467,20 +1486,20 @@
             } else {
                 endHeight = mAmbientState.getStackEndHeight();
             }
-            updateStackHeight(endHeight, fraction);
+            updateInterpolatedStackHeight(endHeight, fraction);
         } else {
             if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) {
                 final float endHeight = updateStackEndHeight(
                         getHeight(), getEmptyBottomMarginInternal(), getTopPadding());
-                updateStackHeight(endHeight, fraction);
+                updateInterpolatedStackHeight(endHeight, fraction);
             } else {
                 // Always updateStackHeight to prevent jumps in the stack height when this fraction
                 // suddenly reapplies after a freeze.
                 final float endHeight = mAmbientState.getStackEndHeight();
-                updateStackHeight(endHeight, fraction);
+                updateInterpolatedStackHeight(endHeight, fraction);
             }
         }
-        if (oldStackHeight != mAmbientState.getStackHeight()) {
+        if (oldStackHeight != mAmbientState.getInterpolatedStackHeight()) {
             requestChildrenUpdate();
         }
     }
@@ -1514,7 +1533,7 @@
     }
 
     @VisibleForTesting
-    public void updateStackHeight(float endHeight, float fraction) {
+    public void updateInterpolatedStackHeight(float endHeight, float fraction) {
         if (!newAodTransition()) {
             // During the (AOD<=>LS) transition where dozeAmount is changing,
             // apply dozeAmount to stack height instead of expansionFraction
@@ -1524,7 +1543,7 @@
                 fraction = 1f - dozeAmount;
             }
         }
-        mAmbientState.setStackHeight(
+        mAmbientState.setInterpolatedStackHeight(
                 MathUtils.lerp(endHeight * StackScrollAlgorithm.START_FRACTION,
                         endHeight, fraction));
     }
@@ -1553,8 +1572,11 @@
 
         // Update the expand progress between started/stopped events
         mAmbientState.setExpansionFraction(expandFraction);
-        // TODO(b/332577544): don't convert to height which then converts to the fraction again
-        setExpandedHeight(expandFraction * getHeight());
+
+        if (!shouldSkipHeightUpdate()) {
+            updateStackEndHeightAndStackHeight(expandFraction);
+            updateExpandedHeight(expandFraction);
+        }
 
         // expansion stopped event requires that the expandFraction has already been updated
         if (!nowExpanding && wasExpanding) {
@@ -1563,12 +1585,32 @@
         }
     }
 
+    private void updateExpandedHeight(float expandFraction) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        float expandedHeight = expandFraction * getHeight();
+        setIsExpanded(expandedHeight > 0);
+
+        if (mExpandedHeight != expandedHeight) {
+            mExpandedHeight = expandedHeight;
+            updateAlgorithmHeightAndPadding();
+            requestChildrenUpdate();
+            notifyAppearChangedListeners();
+        }
+    }
+
+    @Override
+    public void setQsExpandFraction(float expandFraction) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mAmbientState.setQsExpansionFraction(expandFraction);
+    }
+
     /**
      * Update the height of the panel.
      *
      * @param height the expanded height of the panel
      */
     public void setExpandedHeight(float height) {
+        SceneContainerFlag.assertInLegacyMode();
         final boolean skipHeightUpdate = shouldSkipHeightUpdate();
 
         updateStackPosition();
@@ -1592,13 +1634,12 @@
         float translationY;
         float appearFraction = 1.0f;
         boolean appearing = calculateAppearFraction(height) < 1;
-        mAmbientState.setAppearing(appearing);
         if (!appearing) {
             translationY = 0;
             if (mShouldShowShelfOnly) {
                 stackHeight = getTopPadding() + mShelf.getIntrinsicHeight();
             } else if (mQsFullScreen) {
-                int stackStartPosition = mContentHeight - getTopPadding() + mIntrinsicPadding;
+                int stackStartPosition = mContentHeight - getTopPadding() + getIntrinsicPadding();
                 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
                 if (stackStartPosition <= stackEndPosition) {
                     stackHeight = stackEndPosition;
@@ -1701,6 +1742,7 @@
      * Measured relative to the resting position.
      */
     private float getExpandTranslationStart() {
+        SceneContainerFlag.assertInLegacyMode();
         return -getTopPadding() + getMinExpansionHeight() - mShelf.getIntrinsicHeight();
     }
 
@@ -1709,6 +1751,7 @@
      * Measured in absolute height.
      */
     private float getAppearStartPosition() {
+        SceneContainerFlag.assertInLegacyMode();
         if (isHeadsUpTransition()) {
             final NotificationSection firstVisibleSection = getFirstVisibleSection();
             final int pinnedHeight = firstVisibleSection != null
@@ -1764,6 +1807,7 @@
      *    have the shelf on its own)
      */
     private float getAppearEndPosition() {
+        SceneContainerFlag.assertInLegacyMode();
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
             return getAppearEndPositionLegacy();
         }
@@ -1783,7 +1827,7 @@
         } else {
             appearPosition = mEmptyShadeView.getHeight();
         }
-        return appearPosition + (onKeyguard() ? getTopPadding() : mIntrinsicPadding);
+        return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding());
     }
 
     /**
@@ -1809,7 +1853,7 @@
         } else {
             appearPosition = mEmptyShadeView.getHeight();
         }
-        return appearPosition + (onKeyguard() ? getTopPadding() : mIntrinsicPadding);
+        return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding());
     }
 
     private boolean isHeadsUpTransition() {
@@ -1824,7 +1868,7 @@
      */
     @FloatRange(from = -1.0, to = 1.0)
     public float calculateAppearFraction(float height) {
-        if (isHeadsUpTransition()) {
+        if (isHeadsUpTransition() && !SceneContainerFlag.isEnabled()) {
             // HUN is a special case because fraction can go negative if swiping up. And for now
             // it must go negative as other pieces responsible for proper translation up assume
             // negative value for HUN going up.
@@ -1955,7 +1999,8 @@
     }
 
     public void lockScrollTo(View v) {
-        if (mForcedScroll == v) {
+        // NSSL shouldn't handle scrolling with SceneContainer enabled.
+        if (mForcedScroll == v || SceneContainerFlag.isEnabled()) {
             return;
         }
         mForcedScroll = v;
@@ -1963,6 +2008,10 @@
     }
 
     public boolean scrollTo(View v) {
+        // NSSL shouldn't handle scrolling with SceneContainer enabled.
+        if (SceneContainerFlag.isEnabled()) {
+            return false;
+        }
         ExpandableView expandableView = (ExpandableView) v;
         int positionInLinearLayout = getPositionInLinearLayout(v);
         int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
@@ -1984,6 +2033,7 @@
      * the IME.
      */
     private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
+        SceneContainerFlag.assertInLegacyMode();
         return positionInLinearLayout + v.getIntrinsicHeight() +
                 getImeInset() - getHeight()
                 + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
@@ -2293,6 +2343,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);
@@ -2499,10 +2550,33 @@
     }
 
     @VisibleForTesting
-    void updateContentHeight() {
+    void updateStackHeight() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+
+        final int shelfIntrinsicHeight = mShelf != null ? mShelf.getIntrinsicHeight() : 0;
+        final int footerIntrinsicHeight =
+                mFooterView != null ? mFooterView.getIntrinsicHeight() : 0;
+        final int notificationsHeight = (int) mNotificationStackSizeCalculator.computeHeight(
+                /* notificationStackScrollLayout= */ this,
+                mMaxDisplayedNotifications,
+                shelfIntrinsicHeight
+        );
+        mIntrinsicContentHeight = notificationsHeight;
+        final int fullStackHeight = notificationsHeight + footerIntrinsicHeight + mBottomPadding;
+        if (mScrollViewFields.getIntrinsicStackHeight() != fullStackHeight) {
+            mScrollViewFields.setIntrinsicStackHeight(fullStackHeight);
+            notifyStackHeightChangedListeners();
+        }
+    }
+
+    private void updateContentHeight() {
+        if (SceneContainerFlag.isEnabled()) {
+            updateStackHeight();
+            return;
+        }
+
         final float scrimTopPadding = getScrimTopPaddingOrZero();
         final int shelfIntrinsicHeight = mShelf != null ? mShelf.getIntrinsicHeight() : 0;
-        final int footerIntrinsicHeight = mFooterView != null ? mFooterView.getIntrinsicHeight() : 0;
         final float height =
                 (int) scrimTopPadding + (int) mNotificationStackSizeCalculator.computeHeight(
                         /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
@@ -2512,20 +2586,16 @@
         // The topPadding can be bigger than the regular padding when qs is expanded, in that
         // state the maxPanelHeight and the contentHeight should be bigger
         mContentHeight =
-                (int) (height + Math.max(mIntrinsicPadding, getTopPadding()) + mBottomPadding);
-        mScrollViewFields.setIntrinsicStackHeight(
-                (int) (mIntrinsicPadding + mIntrinsicContentHeight + footerIntrinsicHeight
-                        + mBottomPadding));
+                (int) (height + Math.max(getIntrinsicPadding(), getTopPadding()) + mBottomPadding);
         updateScrollability();
         clampScrollPosition();
         updateStackPosition();
         mAmbientState.setContentHeight(mContentHeight);
-
-        notifyStackHeightChangedListeners();
     }
 
     @Override
     public int getIntrinsicStackHeight() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0;
         return mScrollViewFields.getIntrinsicStackHeight();
     }
 
@@ -2534,6 +2604,11 @@
         return getTopHeadsUpIntrinsicHeight();
     }
 
+    @Override
+    public int getHeadsUpInset() {
+        return mHeadsUpInset;
+    }
+
     /**
      * Calculate the gap height between two different views
      *
@@ -2556,6 +2631,9 @@
     }
 
     private void updateScrollability() {
+        if (SceneContainerFlag.isEnabled()) {
+            return;
+        }
         boolean scrollable = !mQsFullScreen && getScrollRange() > 0;
         if (scrollable != mScrollable) {
             mScrollable = scrollable;
@@ -2565,6 +2643,7 @@
     }
 
     private void updateForwardAndBackwardScrollability() {
+        SceneContainerFlag.assertInLegacyMode();
         boolean forwardScrollable = mScrollable && !mScrollAdapter.isScrolledToBottom();
         boolean backwardsScrollable = mScrollable && !mScrollAdapter.isScrolledToTop();
         boolean changed = forwardScrollable != mForwardScrollable
@@ -2696,6 +2775,7 @@
      * @param animate  whether to animate the change
      */
     public void updateTopPadding(float qsHeight, boolean animate) {
+        SceneContainerFlag.assertInLegacyMode();
         int topPadding = (int) qsHeight;
         int minStackHeight = getLayoutMinHeightInternal();
         if (topPadding + minStackHeight > getHeight()) {
@@ -2703,7 +2783,10 @@
         } else {
             mTopPaddingOverflow = 0;
         }
-        setTopPadding(topPadding, animate && !mKeyguardBypassEnabled);
+        if (mAmbientState.getTopPadding() != topPadding) {
+            mAmbientState.setTopPadding(topPadding);
+            onTopPaddingChanged(/* animate = */ animate && !mKeyguardBypassEnabled);
+        }
         setExpandedHeight(mExpandedHeight);
     }
 
@@ -2718,6 +2801,7 @@
     }
 
     private int getLayoutMinHeightInternal() {
+        SceneContainerFlag.assertInLegacyMode();
         if (isHeadsUpTransition()) {
             ExpandableNotificationRow trackedHeadsUpRow = mAmbientState.getTrackedHeadsUpRow();
             if (trackedHeadsUpRow.isAboveShelf()) {
@@ -3525,33 +3609,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));
     }
@@ -3618,7 +3710,7 @@
         if (!isScrollingEnabled()) {
             return false;
         }
-        if (isInsideQsHeader(ev) && !mIsBeingDragged) {
+        if (!isInScrollableRegion(ev) && !mIsBeingDragged) {
             return false;
         }
         mForcedScroll = null;
@@ -3786,11 +3878,26 @@
         return mFlingAfterUpEvent;
     }
 
-    protected boolean isInsideQsHeader(MotionEvent ev) {
-        if (SceneContainerFlag.isEnabled()) {
-            return ev.getY() < mAmbientState.getStackTop();
+    /** Is this touch event inside the scrollable region? */
+    @VisibleForTesting
+    boolean isInScrollableRegion(MotionEvent ev) {
+        if (!SceneContainerFlag.isEnabled()) {
+            return !isInsideQsHeader(ev);
+        }
+        ShadeScrimShape shape = mScrollViewFields.getScrimClippingShape();
+        if (shape == null) {
+            return true; // When there is no scrim, consider this event scrollable.
         }
 
+        ShadeScrimBounds bounds = shape.getBounds();
+        return ev.getX() >= bounds.getLeft()
+                && ev.getX() <= bounds.getRight()
+                && ev.getY() >= bounds.getTop()
+                && ev.getY() <= bounds.getBottom();
+    }
+
+    protected boolean isInsideQsHeader(MotionEvent ev) {
+        SceneContainerFlag.assertInLegacyMode();
         if (QSComposeFragment.isEnabled()) {
             if (mQSHeaderBoundsProvider == null) {
                 return false;
@@ -4092,6 +4199,11 @@
      */
     @Override
     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+        // Don't handle scroll accessibility events from the NSSL, when SceneContainer enabled.
+        if (SceneContainerFlag.isEnabled()) {
+            return super.performAccessibilityActionInternal(action, arguments);
+        }
+
         if (super.performAccessibilityActionInternal(action, arguments)) {
             return true;
         }
@@ -4263,7 +4375,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 {
@@ -4374,7 +4486,7 @@
 
     void onChildAnimationFinished() {
         setAnimationRunning(false);
-        if (NotificationsHeadsUpRefactor.isEnabled()) {
+        if (SceneContainerFlag.isEnabled()) {
             setHeadsUpAnimatingAway(false);
         }
         requestChildrenUpdate();
@@ -4558,10 +4670,20 @@
     }
 
     void setIntrinsicPadding(int intrinsicPadding) {
+        SceneContainerFlag.assertInLegacyMode();
         mIntrinsicPadding = intrinsicPadding;
     }
 
+    /**
+     * Distance from the top of the screen in, where notifications should start when fully expanded
+     * or in the LS.
+     *
+     * Always 0 with SceneContainer enabled.
+     */
     int getIntrinsicPadding() {
+        if (SceneContainerFlag.isEnabled()) {
+            return 0;
+        }
         return mIntrinsicPadding;
     }
 
@@ -4807,6 +4929,7 @@
     }
 
     public boolean isBelowLastNotification(float touchX, float touchY) {
+        SceneContainerFlag.assertInLegacyMode();
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
             ExpandableView child = getChildAtIndex(i);
@@ -4842,6 +4965,11 @@
     @Override
     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
         super.onInitializeAccessibilityEventInternal(event);
+        // Don't handle scroll accessibility events from the NSSL, when SceneContainer enabled.
+        if (SceneContainerFlag.isEnabled()) {
+            return;
+        }
+
         event.setScrollable(mScrollable);
         event.setMaxScrollX(mScrollX);
         event.setScrollY(mOwnScrollY);
@@ -4851,6 +4979,11 @@
     @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
+        // Don't handle scroll accessibility events from the NSSL, when SceneContainer enabled.
+        if (SceneContainerFlag.isEnabled()) {
+            return;
+        }
+
         if (mScrollable) {
             info.setScrollable(true);
             if (mBackwardScrollable) {
@@ -4909,7 +5042,7 @@
     }
 
     public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
-        NotificationsHeadsUpRefactor.assertInLegacyMode();
+        SceneContainerFlag.assertInLegacyMode();
         ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
         generateHeadsUpAnimation(row, isHeadsUp);
     }
@@ -4952,7 +5085,7 @@
             mNeedsAnimation = true;
             if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
                 row.setHeadsUpAnimatingAway(true);
-                if (NotificationsHeadsUpRefactor.isEnabled()) {
+                if (SceneContainerFlag.isEnabled()) {
                     setHeadsUpAnimatingAway(true);
                 }
             }
@@ -5036,10 +5169,12 @@
     }
 
     boolean isQsFullScreen() {
+        SceneContainerFlag.assertInLegacyMode();
         return mQsFullScreen;
     }
 
     public void setQsExpansionFraction(float qsExpansionFraction) {
+        SceneContainerFlag.assertInLegacyMode();
         boolean footerAffected = mQsExpansionFraction != qsExpansionFraction
                 && (mQsExpansionFraction == 1 || qsExpansionFraction == 1);
         mQsExpansionFraction = qsExpansionFraction;
@@ -5083,6 +5218,7 @@
     }
 
     private void updateOnScrollChange() {
+        SceneContainerFlag.assertInLegacyMode();
         if (mScrollListener != null) {
             mScrollListener.accept(mOwnScrollY);
         }
@@ -5146,7 +5282,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 693e8ff..bcdc3bc 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,11 +127,11 @@
 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;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper.HeadsUpNotificationViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -685,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);
@@ -773,7 +773,7 @@
                     mHeadsUpManager,
                     statusBarService.get(),
                     getHeadsUpCallback(),
-                    new HeadsUpNotificationViewControllerEmptyImpl()
+                    getHeadsUpNotificationViewController()
             );
         }
         mNotificationRoundnessManager = notificationRoundnessManager;
@@ -879,7 +879,7 @@
             });
         }
 
-        if (!NotificationsHeadsUpRefactor.isEnabled()) {
+        if (!SceneContainerFlag.isEnabled()) {
             mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         }
         mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
@@ -1186,6 +1186,7 @@
     }
 
     public void setIntrinsicPadding(int intrinsicPadding) {
+        SceneContainerFlag.assertInLegacyMode();
         mView.setIntrinsicPadding(intrinsicPadding);
     }
 
@@ -1233,6 +1234,7 @@
     }
 
     public boolean isBelowLastNotification(float x, float y) {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.isBelowLastNotification(x, y);
     }
 
@@ -1273,6 +1275,7 @@
     }
 
     public void setQsExpansionFraction(float expansionFraction) {
+        SceneContainerFlag.assertInLegacyMode();
         mView.setQsExpansionFraction(expansionFraction);
     }
 
@@ -1326,6 +1329,7 @@
     }
 
     public int getTopPadding() {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.getTopPadding();
     }
 
@@ -1405,6 +1409,7 @@
     }
 
     public float calculateAppearFraction(float height) {
+        SceneContainerFlag.assertInLegacyMode();
         return mView.calculateAppearFraction(height);
     }
 
@@ -1504,7 +1509,7 @@
     }
 
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
-        NotificationsHeadsUpRefactor.assertInLegacyMode();
+        SceneContainerFlag.assertInLegacyMode();
         mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
     }
 
@@ -1686,7 +1691,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();
@@ -1699,15 +1704,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
@@ -1850,6 +1875,32 @@
         return mTouchHandler;
     }
 
+    private HeadsUpNotificationViewController getHeadsUpNotificationViewController() {
+        HeadsUpNotificationViewController headsUpViewController;
+        if (SceneContainerFlag.isEnabled()) {
+            headsUpViewController = new HeadsUpNotificationViewController() {
+                @Override
+                public void setHeadsUpDraggingStartingHeight(int startHeight) {
+                    // do nothing
+                }
+
+                @Override
+                public void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow) {
+                    setTrackingHeadsUp(expandableNotificationRow);
+                }
+
+                @Override
+                public void startExpand(float newX, float newY, boolean startTracking,
+                        float expandedHeight) {
+                    // do nothing
+                }
+            };
+        } else {
+            headsUpViewController = new HeadsUpNotificationViewControllerEmptyImpl();
+        }
+        return headsUpViewController;
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("mMaxAlphaFromView=" + mMaxAlphaFromView);
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/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index aee1d3e..ef1bcfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static androidx.core.math.MathUtils.clamp;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -577,7 +575,8 @@
         final float shelfHeight = showingShelf ? ambientState.getShelf().getIntrinsicHeight() : 0f;
         final float scrimPadding = getScrimTopPaddingOrZero(ambientState);
 
-        final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
+        final float stackHeight =
+                ambientState.getInterpolatedStackHeight() - shelfHeight - scrimPadding;
         final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
         if (stackEndHeight == 0f) {
             // This should not happen, since even when the shade is empty we show EmptyShadeView
@@ -736,7 +735,7 @@
                             || ambientState.getDozeAmount() == 1f
                             || bypassPulseNotExpanding
                             ? ambientState.getInnerHeight()
-                            : ambientState.getStackHeight();
+                            : ambientState.getInterpolatedStackHeight();
                     final float shelfStart = stackBottom
                             - ambientState.getShelf().getIntrinsicHeight()
                             - mPaddingBetweenElements;
@@ -890,7 +889,14 @@
                 continue;
             }
             ExpandableViewState childState = row.getViewState();
-            if (topHeadsUpEntry == null && row.mustStayOnScreen() && !childState.headsUpIsVisible) {
+            boolean shouldSetTopHeadsUpEntry;
+            if (SceneContainerFlag.isEnabled()) {
+                shouldSetTopHeadsUpEntry = row.isHeadsUp();
+            } else {
+                shouldSetTopHeadsUpEntry = row.mustStayOnScreen();
+            }
+            if (topHeadsUpEntry == null && shouldSetTopHeadsUpEntry
+                    && !childState.headsUpIsVisible) {
                 topHeadsUpEntry = row;
                 childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
             }
@@ -898,7 +904,7 @@
             float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
             if (mIsExpanded) {
                 if (SceneContainerFlag.isEnabled()) {
-                    if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
+                    if (shouldHunBeVisibleWhenScrolled(row.isHeadsUp(),
                             childState.headsUpIsVisible, row.showingPulsing(),
                             ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
                         // the height of this child before clamping it to the top
@@ -909,10 +915,19 @@
                                 /* viewState = */ childState
                         );
                         float baseZ = ambientState.getBaseZHeight();
-                        if (headsUpTranslation < ambientState.getStackTop()) {
-                            // HUN displayed above the stack top, it needs a fix shadow
-                            childState.setZTranslation(baseZ + mPinnedZTranslationExtra);
-                        } else {
+                        if (headsUpTranslation > ambientState.getStackTop()
+                                && row.isAboveShelf()) {
+                            // HUN displayed outside of the stack during transition from Gone/LS;
+                            // add a shadow that corresponds to the transition progress.
+                            float fraction = 1 - ambientState.getExpansionFraction();
+                            childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+                        } else if (headsUpTranslation < ambientState.getStackTop()
+                                && row.isAboveShelf()) {
+                            // HUN displayed outside of the stack during transition from QS;
+                            // add a shadow that corresponds to the transition progress.
+                            float fraction = ambientState.getQsExpansionFraction();
+                            childState.setZTranslation(baseZ + fraction * mPinnedZTranslationExtra);
+                        } else if (headsUpTranslation > ambientState.getStackTop()) {
                             // HUN displayed within the stack, add a shadow if it overlaps with
                             // other elements.
                             //
@@ -927,6 +942,8 @@
                                     /* baseZ = */ baseZ,
                                     /* viewState = */ childState
                             );
+                        } else {
+                            childState.setZTranslation(baseZ);
                         }
                         if (isTopEntry && row.isAboveShelf()) {
                             clampHunToMaxTranslation(
@@ -1081,7 +1098,7 @@
         if (scrollingContentTopPadding > 0f) {
             // scrollingContentTopPadding makes a gap between the bottom of the HUN and the top
             // of the scrolling content. Use this to animate to the full shadow.
-            shadowFraction = clamp(overlap / scrollingContentTopPadding, 0f, 1f);
+            shadowFraction = Math.clamp(overlap / scrollingContentTopPadding, 0f, 1f);
         }
 
         if (overlap > 0.0f) {
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 6226fe7..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>?)
 
@@ -80,9 +83,24 @@
     /** sets the current expand fraction */
     fun setExpandFraction(expandFraction: Float)
 
+    /** sets the current QS expand fraction */
+    fun setQsExpandFraction(expandFraction: Float)
+
     /** Sets whether the view is displayed in doze mode. */
     fun setDozing(dozing: Boolean)
 
+    /** Sets whether the view is displayed in pulsing mode. */
+    fun setPulsing(pulsing: Boolean, animated: Boolean)
+
+    /** 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 950b14d..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 ->
@@ -85,16 +87,31 @@
             launch {
                 viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) }
             }
+            launch { viewModel.qsExpandFraction.collect { view.setQsExpandFraction(it) } }
             launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
             launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
-            launch { viewModel.shouldResetStackTop.filter { it }.collect { view.setStackTop(0f) } }
+            launch {
+                viewModel.isPulsing.collect { isPulsing ->
+                    view.setPulsing(isPulsing, viewModel.shouldAnimatePulse.value)
+                }
+            }
+            launch {
+                viewModel.shouldResetStackTop
+                    .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 ed69e6f..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
@@ -39,6 +41,8 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
@@ -58,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,
+    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
@@ -107,28 +115,31 @@
                 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 ->
-                        expandFractionForTransition(
+                    is Idle -> if (expandedInScene(transitionState.currentScene)) 1f else 0f
+                    is ChangeScene ->
+                        expandFractionDuringSceneChange(
                             transitionState,
                             shadeExpansion,
-                            shadeMode,
                             qsExpansion,
-                            quickSettingsScene
                         )
+                    is Transition.ShowOrHideOverlay,
+                    is Transition.ReplaceOverlay -> TODO("b/359173565: Handle overlay transitions")
                 }
             }
             .distinctUntilChanged()
             .dumpWhileCollecting("expandFraction")
 
+    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")
 
@@ -192,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
@@ -210,13 +225,30 @@
         }
     }
 
+    /** Whether the notification stack is displayed in pulsing mode. */
+    val isPulsing: Flow<Boolean> by lazy {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+            flowOf(false)
+        } else {
+            keyguardInteractor.get().isPulsing.dumpWhileCollecting("isPulsing")
+        }
+    }
+
+    val shouldAnimatePulse: StateFlow<Boolean> by lazy {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+            MutableStateFlow(false)
+        } else {
+            keyguardInteractor.get().isAodAvailable
+        }
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): NotificationScrollViewModel
     }
 }
 
-private fun ObservableTransitionState.Transition.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 3dd265b..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);
@@ -2365,9 +2369,14 @@
                 // lock screen where users can use the UDFPS affordance to enter the device
                 mStatusBarKeyguardViewManager.reset(true);
             } else if (mState == StatusBarState.KEYGUARD
-                    && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
-                    && mStatusBarKeyguardViewManager.isSecure()) {
-                if (!relockWithPowerButtonImmediately()) {
+                    && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
+                boolean needsBouncer = mStatusBarKeyguardViewManager.isSecure();
+                if (relockWithPowerButtonImmediately()) {
+                    // Only request if SIM bouncer is needed
+                    needsBouncer = mStatusBarKeyguardViewManager.needsFullscreenBouncer();
+                }
+
+                if (needsBouncer) {
                     Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
                     if (SceneContainerFlag.isEnabled()) {
                         mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 2e1ab38..bb5aa23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -20,16 +20,17 @@
 import android.graphics.Rect
 import android.os.LocaleList
 import android.view.View.LAYOUT_DIRECTION_RTL
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 
-@SysUISingleton
-class ConfigurationControllerImpl @Inject constructor(
-        @Application context: Context,
-        ) : ConfigurationController {
+class ConfigurationControllerImpl
+@AssistedInject
+constructor(
+    @Assisted private val context: Context,
+) : ConfigurationController {
 
     private val listeners: MutableList<ConfigurationListener> = ArrayList()
     private val lastConfig = Configuration()
@@ -40,18 +41,17 @@
     private val inCarMode: Boolean
     private var uiMode: Int = 0
     private var localeList: LocaleList? = null
-    private val context: Context
     private var layoutDirection: Int
     private var orientation = Configuration.ORIENTATION_UNDEFINED
 
     init {
         val currentConfig = context.resources.configuration
-        this.context = context
         fontScale = currentConfig.fontScale
         density = currentConfig.densityDpi
         smallestScreenWidth = currentConfig.smallestScreenWidthDp
         maxBounds.set(currentConfig.windowConfiguration.maxBounds)
-        inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
+        inCarMode =
+            currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
                 Configuration.UI_MODE_TYPE_CAR
         uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
         localeList = currentConfig.locales
@@ -60,29 +60,20 @@
 
     override fun notifyThemeChanged() {
         // Avoid concurrent modification exception
-        val listeners = synchronized(this.listeners) {
-           ArrayList(this.listeners)
-        }
+        val listeners = synchronized(this.listeners) { ArrayList(this.listeners) }
 
-        listeners.filterForEach({ this.listeners.contains(it) }) {
-            it.onThemeChanged()
-        }
+        listeners.filterForEach({ this.listeners.contains(it) }) { it.onThemeChanged() }
     }
 
     override fun onConfigurationChanged(newConfig: Configuration) {
         // Avoid concurrent modification exception
-        val listeners = synchronized(this.listeners) {
-           ArrayList(this.listeners)
-        }
-        listeners.filterForEach({ this.listeners.contains(it) }) {
-            it.onConfigChanged(newConfig)
-        }
+        val listeners = synchronized(this.listeners) { ArrayList(this.listeners) }
+        listeners.filterForEach({ this.listeners.contains(it) }) { it.onConfigChanged(newConfig) }
         val fontScale = newConfig.fontScale
         val density = newConfig.densityDpi
         val uiMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
         val uiModeChanged = uiMode != this.uiMode
-        if (density != this.density || fontScale != this.fontScale ||
-                inCarMode && uiModeChanged) {
+        if (density != this.density || fontScale != this.fontScale || inCarMode && uiModeChanged) {
             listeners.filterForEach({ this.listeners.contains(it) }) {
                 it.onDensityOrFontScaleChanged()
             }
@@ -105,17 +96,13 @@
             // would be a direct reference to windowConfiguration.maxBounds, so the if statement
             // above would always fail. See b/245799099 for more information.
             this.maxBounds.set(maxBounds)
-            listeners.filterForEach({ this.listeners.contains(it) }) {
-                it.onMaxBoundsChanged()
-            }
+            listeners.filterForEach({ this.listeners.contains(it) }) { it.onMaxBoundsChanged() }
         }
 
         val localeList = newConfig.locales
         if (localeList != this.localeList) {
             this.localeList = localeList
-            listeners.filterForEach({ this.listeners.contains(it) }) {
-                it.onLocaleListChanged()
-            }
+            listeners.filterForEach({ this.listeners.contains(it) }) { it.onLocaleListChanged() }
         }
 
         if (uiModeChanged) {
@@ -124,9 +111,7 @@
             context.theme.applyStyle(context.themeResId, true)
 
             this.uiMode = uiMode
-            listeners.filterForEach({ this.listeners.contains(it) }) {
-                it.onUiModeChanged()
-            }
+            listeners.filterForEach({ this.listeners.contains(it) }) { it.onUiModeChanged() }
         }
 
         if (layoutDirection != newConfig.layoutDirection) {
@@ -137,9 +122,7 @@
         }
 
         if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) {
-            listeners.filterForEach({ this.listeners.contains(it) }) {
-                it.onThemeChanged()
-            }
+            listeners.filterForEach({ this.listeners.contains(it) }) { it.onThemeChanged() }
         }
 
         val newOrientation = newConfig.orientation
@@ -152,16 +135,12 @@
     }
 
     override fun addCallback(listener: ConfigurationListener) {
-        synchronized(listeners) {
-            listeners.add(listener)
-        }
+        synchronized(listeners) { listeners.add(listener) }
         listener.onDensityOrFontScaleChanged()
     }
 
     override fun removeCallback(listener: ConfigurationListener) {
-        synchronized(listeners) {
-            listeners.remove(listener)
-        }
+        synchronized(listeners) { listeners.remove(listener) }
     }
 
     override fun isLayoutRtl(): Boolean {
@@ -176,6 +155,15 @@
             else -> "err"
         }
     }
+
+    @AssistedFactory
+    interface Factory {
+        /**
+         * Creates a [ConfigurationController] that uses [context] to resolve the current
+         * configuration and resources.
+         */
+        fun create(context: Context): ConfigurationControllerImpl
+    }
 }
 
 // This could be done with a Collection.filter and Collection.forEach, but Collection.filter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
index 90ebaf2..8f4279e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
@@ -26,7 +27,7 @@
 class ConfigurationControllerStartable
 @Inject
 constructor(
-    private val configurationController: ConfigurationController,
+    @GlobalConfig private val configurationController: ConfigurationController,
     private val listeners: Set<@JvmSuppressWildcards ConfigurationListener>
 ) : CoreStartable {
     override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 0067316..f649418 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -25,6 +25,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 
 import javax.inject.Inject;
 
@@ -124,6 +125,11 @@
 
         // Begin pulse. Note that it's very important that the pulse finished callback
         // be invoked when we're done so that the caller can drop the pulse wakelock.
+        if (SceneContainerFlag.isEnabled()) {
+            // ScrimController.Callback#onDisplayBlanked is no longer triggered when flexiglass is
+            // on, but we still need to signal that pulsing has started.
+            callback.onPulseStarted();
+        }
         mPulseCallback = callback;
         mPulseReason = reason;
     }
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/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index c3da7fc..178c318 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -435,13 +435,14 @@
 
     /** Should only be called from {@link KeyguardStatusBarViewController}. */
     void onOverlayChanged() {
-        int theme = Utils.getThemeAttr(mContext, com.android.internal.R.attr.textAppearanceSmall);
-        mCarrierLabel.setTextAppearance(theme);
+        final int carrierTheme = R.style.TextAppearance_StatusBar_Clock;
+        mCarrierLabel.setTextAppearance(carrierTheme);
         mBatteryView.updatePercentView();
 
+        final int userSwitcherTheme = R.style.TextAppearance_StatusBar_UserChip;
         TextView userSwitcherName = mUserSwitcherContainer.findViewById(R.id.current_user_name);
         if (userSwitcherName != null) {
-            userSwitcherName.setTextAppearance(theme);
+            userSwitcherName.setTextAppearance(userSwitcherTheme);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 56ea00c..7ef1e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -22,6 +22,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.UserTracker;
@@ -43,17 +44,20 @@
     private final UserManager mUserManager;
     private final UserTracker mUserTracker;
     private final LinkedList<UserInfo> mProfiles;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     private boolean mListening;
     private int mCurrentUser;
 
     @Inject
     public ManagedProfileControllerImpl(Context context, @Main Executor mainExecutor,
-            UserTracker userTracker, UserManager userManager) {
+            UserTracker userTracker, UserManager userManager,
+            KeyguardUpdateMonitor keyguardUpdateMonitor) {
         mContext = context;
         mMainExecutor = mainExecutor;
         mUserManager = userManager;
         mUserTracker = userTracker;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mProfiles = new LinkedList<>();
     }
 
@@ -80,6 +84,7 @@
                     StatusBarManager statusBarManager = (StatusBarManager) mContext
                             .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
                     statusBarManager.collapsePanels();
+                    mKeyguardUpdateMonitor.awakenFromDream();
                 }
             }
         }
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..ba39c3b 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.internal.statusbar.StatusBarIcon;
 import com.android.systemui.Flags;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -78,6 +79,8 @@
 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.statusbar.policy.domain.model.ZenModeInfo;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.DateFormatUtil;
@@ -99,7 +102,6 @@
                 CommandQueue.Callbacks,
                 RotationLockControllerCallback,
                 Listener,
-                ZenModeController.Callback,
                 DeviceProvisionedListener,
                 KeyguardStateController.Callback,
                 PrivacyItemController.Callback,
@@ -161,6 +163,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 +196,7 @@
             PrivacyItemController privacyItemController,
             PrivacyLogger privacyLogger,
             ConnectedDisplayInteractor connectedDisplayInteractor,
+            ZenModeInteractor zenModeInteractor,
             JavaAdapter javaAdapter
     ) {
         mIconController = iconController;
@@ -224,6 +228,7 @@
         mTelecomManager = telecomManager;
         mRingerModeTracker = ringerModeTracker;
         mPrivacyLogger = privacyLogger;
+        mZenModeInteractor = zenModeInteractor;
         mJavaAdapter = javaAdapter;
 
         mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
@@ -355,7 +360,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::onMainActiveModeChanged);
+        }
+        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 +396,43 @@
                 () -> mResources.getString(R.string.accessibility_managed_profile));
     }
 
-    @Override
-    public void onZenChanged(int zen) {
-        updateVolumeZen();
+    private void onMainActiveModeChanged(@Nullable ZenModeInfo mainActiveMode) {
+        if (!usesModeIcons()) {
+            Log.wtf(TAG, "onMainActiveModeChanged shouldn't run if MODES_UI_ICONS is disabled");
+            return;
+        }
+
+        boolean visible = mainActiveMode != null;
+        if (visible) {
+            // Shape=FIXED_SPACE because mode icons can be from 3P packages and may not be square;
+            // we don't want to allow apps to set incredibly wide icons and take up too much space
+            // in the status bar.
+            mIconController.setResourceIcon(mSlotZen,
+                    mainActiveMode.getIcon().key().resPackage(),
+                    mainActiveMode.getIcon().key().resId(),
+                    mainActiveMode.getIcon().drawable(),
+                    mainActiveMode.getName(),
+                    StatusBarIcon.Shape.FIXED_SPACE);
+        }
+        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 +456,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 +488,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 +514,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 +942,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/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 5be4ba2..4a0fdee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -121,7 +121,7 @@
     private MultiSourceMinAlphaController mEndSideAlphaController;
     private LinearLayout mEndSideContent;
     private View mClockView;
-    private View mOngoingActivityChip;
+    private View mPrimaryOngoingActivityChip;
     private View mNotificationIconAreaInner;
     // Visibilities come in from external system callers via disable flags, but we also sometimes
     // modify the visibilities internally. We need to store both so that we don't accidentally
@@ -354,7 +354,7 @@
         mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
         mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent);
         mClockView = mStatusBar.findViewById(R.id.clock);
-        mOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip);
+        mPrimaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_primary);
         showEndSideContent(false);
         showClock(false);
         initOperatorName();
@@ -636,9 +636,9 @@
         // icons so if the icons are disabled then the activity chip should be, too.)
         boolean showOngoingActivityChip = hasOngoingActivity && !disableNotifications;
         if (showOngoingActivityChip) {
-            showOngoingActivityChip(animate);
+            showPrimaryOngoingActivityChip(animate);
         } else {
-            hideOngoingActivityChip(animate);
+            hidePrimaryOngoingActivityChip(animate);
         }
     }
 
@@ -729,20 +729,20 @@
         animateShow(mClockView, animate);
     }
 
-    /** Hides the ongoing activity chip. */
-    private void hideOngoingActivityChip(boolean animate) {
-        animateHiddenState(mOngoingActivityChip, View.GONE, animate);
+    /** Hides the primary ongoing activity chip. */
+    private void hidePrimaryOngoingActivityChip(boolean animate) {
+        animateHiddenState(mPrimaryOngoingActivityChip, View.GONE, animate);
     }
 
     /**
-     * Displays the ongoing activity chip.
+     * Displays the primary ongoing activity chip.
      *
      * If Flags.statusBarScreenSharingChips is disabled, this chip will only ever contain the
      * ongoing call information, If that flag is enabled, it will support different kinds of ongoing
      * activities. See b/332662551.
      */
-    private void showOngoingActivityChip(boolean animate) {
-        animateShow(mOngoingActivityChip, animate);
+    private void showPrimaryOngoingActivityChip(boolean animate) {
+        animateShow(mPrimaryOngoingActivityChip, animate);
     }
 
     /**
@@ -850,7 +850,7 @@
 
     private void initOngoingCallChip() {
         mOngoingCallController.addCallback(mOngoingCallListener);
-        mOngoingCallController.setChipView(mOngoingActivityChip);
+        mOngoingCallController.setChipView(mPrimaryOngoingActivityChip);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java
index 8871dae..6c30330 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone.ui;
 
-import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
 import com.android.internal.statusbar.StatusBarIcon;
@@ -64,9 +63,8 @@
     }
 
     @Override
-    protected LinearLayout.LayoutParams onCreateLayoutParams() {
-        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
+    protected LinearLayout.LayoutParams onCreateLayoutParams(StatusBarIcon.Shape shape) {
+        LinearLayout.LayoutParams lp = super.onCreateLayoutParams(shape);
         lp.setMargins(mIconHorizontalMargin, 0, mIconHorizontalMargin, 0);
         return lp;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
index 5ad7376..91ead61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
+import static com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl.usesModeIcons;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -27,9 +28,8 @@
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
-import androidx.annotation.VisibleForTesting;
-
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarIcon.Shape;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -155,12 +155,11 @@
         };
     }
 
-    @VisibleForTesting
     protected StatusBarIconView addIcon(int index, String slot, boolean blocked,
             StatusBarIcon icon) {
         StatusBarIconView view = onCreateStatusBarIconView(slot, blocked);
         view.set(icon);
-        mGroup.addView(view, index, onCreateLayoutParams());
+        mGroup.addView(view, index, onCreateLayoutParams(icon.shape));
         return view;
     }
 
@@ -174,7 +173,7 @@
             int index) {
         mBindableIcons.put(holder.getSlot(), holder);
         ModernStatusBarView view = holder.getInitializer().createAndBind(mContext);
-        mGroup.addView(view, index, onCreateLayoutParams());
+        mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT));
         if (mIsInDemoMode) {
             mDemoStatusIcons.addBindableIcon(holder);
         }
@@ -183,7 +182,7 @@
 
     protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
         ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
-        mGroup.addView(view, index, onCreateLayoutParams());
+        mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT));
 
         if (mIsInDemoMode) {
             mDemoStatusIcons.addModernWifiView(mWifiViewModel);
@@ -199,7 +198,7 @@
             int subId
     ) {
         BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId);
-        mGroup.addView(view, index, onCreateLayoutParams());
+        mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT));
 
         if (mIsInDemoMode) {
             Context mobileContext = mMobileContextProvider
@@ -233,8 +232,12 @@
                 );
     }
 
-    protected LinearLayout.LayoutParams onCreateLayoutParams() {
-        return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
+    protected LinearLayout.LayoutParams onCreateLayoutParams(Shape shape) {
+        int width = usesModeIcons() && shape == StatusBarIcon.Shape.FIXED_SPACE
+                ? mIconSize
+                : ViewGroup.LayoutParams.WRAP_CONTENT;
+
+        return new LinearLayout.LayoutParams(width, mIconSize);
     }
 
     protected void destroy() {
@@ -256,6 +259,13 @@
     /** Called once an icon has been set. */
     public void onSetIcon(int viewIndex, StatusBarIcon icon) {
         StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
+        if (usesModeIcons()) {
+            ViewGroup.LayoutParams current = view.getLayoutParams();
+            ViewGroup.LayoutParams desired = onCreateLayoutParams(icon.shape);
+            if (desired.width != current.width || desired.height != current.height) {
+                view.setLayoutParams(desired);
+            }
+        }
         view.set(icon);
     }
 
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..0459b97 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,19 @@
     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,
+            StatusBarIcon.Shape shape);
+
+    /**
      * 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..9b6d32b 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,69 @@
         }
     }
 
-    /** */
     @Override
     public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
+        setResourceIconInternal(
+                slot,
+                Icon.createWithResource(mContext, resourceId),
+                /* preloadedIcon= */ null,
+                contentDescription,
+                StatusBarIcon.Type.SystemIcon,
+                StatusBarIcon.Shape.WRAP_CONTENT);
+    }
+
+    @Override
+    public void setResourceIcon(String slot, @Nullable String resPackage,
+            @DrawableRes int iconResId, @Nullable Drawable preloadedIcon,
+            CharSequence contentDescription, StatusBarIcon.Shape shape) {
+        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,
+                shape);
+    }
+
+    private void setResourceIconInternal(String slot, Icon resourceIcon,
+            @Nullable Drawable preloadedIcon, CharSequence contentDescription,
+            StatusBarIcon.Type type, StatusBarIcon.Shape shape) {
+        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, shape);
+            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().shape = shape;
+            holder.getIcon().preloadedIcon = preloadedIcon;
             handleSet(slot, holder);
         }
     }
@@ -524,4 +580,9 @@
             return slot + EXTERNAL_SLOT_SUFFIX;
         }
     }
+
+    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/mobile/data/model/ServiceStateModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt
deleted file mode 100644
index cce3eb0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.model
-
-import android.telephony.ServiceState
-
-/**
- * Simplified representation of a [ServiceState] for use in SystemUI. Add any fields that we need to
- * extract from service state here for consumption downstream
- */
-data class ServiceStateModel(val isEmergencyOnly: Boolean) {
-    companion object {
-        fun fromServiceState(serviceState: ServiceState): ServiceStateModel {
-            return ServiceStateModel(isEmergencyOnly = serviceState.isEmergencyOnly)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 5ad8bf1..32e9c85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -21,7 +21,6 @@
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.MobileMappings.Config
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
@@ -93,17 +92,15 @@
     val defaultMobileIconGroup: Flow<MobileIconGroup>
 
     /**
-     * [deviceServiceState] is equivalent to the last [Intent.ACTION_SERVICE_STATE] broadcast with a
-     * subscriptionId of -1 (aka [SubscriptionManager.INVALID_SUBSCRIPTION_ID]).
+     * Can the device make emergency calls using the device-based service state? This field is only
+     * useful when all known active subscriptions are OOS and not emergency call capable.
      *
-     * While each [MobileConnectionsRepository] listens for the service state of each subscription,
-     * there is potentially a service state associated with the device itself. This value can be
-     * used to calculate e.g., the emergency calling capability of the device (as opposed to the
-     * emergency calling capability of an individual mobile connection)
+     * Specifically, this checks every [ServiceState] of the device, and looks for any that report
+     * [ServiceState.isEmergencyOnly].
      *
-     * Note: this is a [StateFlow] using an eager sharing strategy.
+     * This is an eager flow, and re-evaluates whenever ACTION_SERVICE_STATE is sent for subId = -1.
      */
-    val deviceServiceState: StateFlow<ServiceStateModel?>
+    val isDeviceEmergencyCallCapable: StateFlow<Boolean>
 
     /**
      * If any active SIM on the device is in
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index b068152..b247da4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
@@ -152,16 +151,17 @@
     override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
         activeRepo.flatMapLatest { it.defaultMobileIconGroup }
 
-    override val deviceServiceState: StateFlow<ServiceStateModel?> =
+    override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
         activeRepo
-            .flatMapLatest { it.deviceServiceState }
+            .flatMapLatest { it.isDeviceEmergencyCallCapable }
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                realRepository.deviceServiceState.value
+                realRepository.isDeviceEmergencyCallCapable.value
             )
 
     override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure }
+
     override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure()
 
     override val defaultDataSubId: StateFlow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index a944e91..3a79f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
@@ -137,10 +136,11 @@
 
     override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
 
-    // TODO(b/339023069): demo command for device-based connectivity state
-    override val deviceServiceState: StateFlow<ServiceStateModel?> = MutableStateFlow(null)
+    // TODO(b/339023069): demo command for device-based emergency calls state
+    override val isDeviceEmergencyCallCapable: StateFlow<Boolean> = MutableStateFlow(false)
 
     override val isAnySimSecure: Flow<Boolean> = flowOf(getIsAnySimSecure())
+
     override fun getIsAnySimSecure(): Boolean = false
 
     override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 261258a..b756a05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -21,7 +21,6 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.telephony.CarrierConfigManager
-import android.telephony.ServiceState
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
@@ -49,7 +48,6 @@
 import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
@@ -72,7 +70,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
@@ -175,8 +172,8 @@
             }
             .flowOn(bgDispatcher)
 
-    /** Note that this flow is eager, so we don't miss any state */
-    override val deviceServiceState: StateFlow<ServiceStateModel?> =
+    /** Turn ACTION_SERVICE_STATE (for subId = -1) into an event */
+    private val serviceStateChangedEvent: Flow<Unit> =
         broadcastDispatcher
             .broadcastFlow(IntentFilter(Intent.ACTION_SERVICE_STATE)) { intent, _ ->
                 val subId =
@@ -185,24 +182,34 @@
                         INVALID_SUBSCRIPTION_ID
                     )
 
-                val extras = intent.extras
-                if (extras == null) {
-                    logger.logTopLevelServiceStateBroadcastMissingExtras(subId)
-                    return@broadcastFlow null
-                }
-
-                val serviceState = ServiceState.newFromBundle(extras)
-                logger.logTopLevelServiceStateBroadcastEmergencyOnly(subId, serviceState)
+                // Only emit if the subId is not associated with an active subscription
                 if (subId == INVALID_SUBSCRIPTION_ID) {
-                    // Assume that -1 here is the device's service state. We don't care about
-                    // other ones.
-                    ServiceStateModel.fromServiceState(serviceState)
-                } else {
-                    null
+                    Unit
                 }
             }
-            .filterNotNull()
-            .stateIn(scope, SharingStarted.Eagerly, null)
+            // Emit on start so that we always check the state at least once
+            .onStart { emit(Unit) }
+
+    /** Eager flow to determine the device-based emergency calls only state */
+    override val isDeviceEmergencyCallCapable: StateFlow<Boolean> =
+        serviceStateChangedEvent
+            .mapLatest {
+                val modems = telephonyManager.activeModemCount
+                // Check the service state for every modem. If any state reports emergency calling
+                // capable, then consider the device to have emergency call capabilities
+                (0..<modems)
+                    .map { telephonyManager.getServiceStateForSlot(it) }
+                    .any { it?.isEmergencyOnly == true }
+            }
+            .flowOn(bgDispatcher)
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogger,
+                columnPrefix = LOGGING_PREFIX,
+                columnName = "deviceEmergencyOnly",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.Eagerly, false)
 
     /**
      * State flow that emits the set of mobile data subscriptions, each represented by its own
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 26553e6..28fff4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -385,15 +385,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> =
-        mobileConnectionsRepo.deviceServiceState
-            .map { it?.isEmergencyOnly ?: false }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLogger,
-                columnPrefix = LOGGING_PREFIX,
-                columnName = "deviceEmergencyOnly",
-                initialValue = false,
-            )
+        mobileConnectionsRepo.isDeviceEmergencyCallCapable
 
     /** Vends out new [MobileIconInteractor] for a particular subId */
     override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 199b5b67..37f2f19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -36,14 +36,12 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -76,37 +74,10 @@
     @DeviceBasedSatelliteInputLog logBuffer: LogBuffer,
     @DeviceBasedSatelliteTableLog tableLog: TableLogBuffer,
 ) : DeviceBasedSatelliteViewModel {
-    private val shouldShowIcon: Flow<Boolean> =
-        interactor.areAllConnectionsOutOfService
-            .flatMapLatest { allOos ->
-                if (!allOos) {
-                    flowOf(false)
-                } else {
-                    combine(
-                        interactor.isSatelliteAllowed,
-                        interactor.isSatelliteProvisioned,
-                        interactor.isWifiActive,
-                        airplaneModeRepository.isAirplaneMode
-                    ) { isSatelliteAllowed, isSatelliteProvisioned, isWifiActive, isAirplaneMode ->
-                        isSatelliteAllowed &&
-                            isSatelliteProvisioned &&
-                            !isWifiActive &&
-                            !isAirplaneMode
-                    }
-                }
-            }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLog,
-                columnPrefix = "vm",
-                columnName = COL_VISIBLE_CONDITION,
-                initialValue = false,
-            )
 
     // This adds a 10 seconds delay before showing the icon
-    private val shouldActuallyShowIcon: StateFlow<Boolean> =
-        shouldShowIcon
-            .distinctUntilChanged()
+    private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
+        interactor.areAllConnectionsOutOfService
             .flatMapLatest { shouldShow ->
                 if (shouldShow) {
                     logBuffer.log(
@@ -125,6 +96,45 @@
             .logDiffsForTable(
                 tableLog,
                 columnPrefix = "vm",
+                columnName = COL_VISIBLE_FOR_OOS,
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    private val canShowIcon =
+        combine(
+            interactor.isSatelliteAllowed,
+            interactor.isSatelliteProvisioned,
+        ) { allowed, provisioned ->
+            allowed && provisioned
+        }
+
+    private val showIcon =
+        canShowIcon
+            .flatMapLatest { canShow ->
+                if (!canShow) {
+                    flowOf(false)
+                } else {
+                    combine(
+                        shouldShowIconForOosAfterHysteresis,
+                        interactor.connectionState,
+                        interactor.isWifiActive,
+                        airplaneModeRepository.isAirplaneMode,
+                    ) { showForOos, connectionState, isWifiActive, isAirplaneMode ->
+                        if (isWifiActive || isAirplaneMode) {
+                            false
+                        } else {
+                            showForOos ||
+                                connectionState == SatelliteConnectionState.On ||
+                                connectionState == SatelliteConnectionState.Connected
+                        }
+                    }
+                }
+            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLog,
+                columnPrefix = "vm",
                 columnName = COL_VISIBLE,
                 initialValue = false,
             )
@@ -132,7 +142,7 @@
 
     override val icon: StateFlow<Icon?> =
         combine(
-                shouldActuallyShowIcon,
+                showIcon,
                 interactor.connectionState,
                 interactor.signalStrength,
             ) { shouldShow, state, signalStrength ->
@@ -146,7 +156,7 @@
 
     override val carrierText: StateFlow<String?> =
         combine(
-                shouldActuallyShowIcon,
+                showIcon,
                 interactor.connectionState,
             ) { shouldShow, connectionState ->
                 logBuffer.log(
@@ -156,7 +166,7 @@
                         bool1 = shouldShow
                         str1 = connectionState.name
                     },
-                    { "Updating carrier text. shouldActuallyShow=$bool1 connectionState=$str1" }
+                    { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
                 )
                 if (shouldShow) {
                     when (connectionState) {
@@ -165,28 +175,30 @@
                             context.getString(R.string.satellite_connected_carrier_text)
                         SatelliteConnectionState.Off,
                         SatelliteConnectionState.Unknown -> {
-                            null
+                            // If we're showing the satellite icon opportunistically, use the
+                            // emergency-only version of the carrier string
+                            context.getString(R.string.satellite_emergency_only_carrier_text)
                         }
                     }
                 } else {
                     null
                 }
             }
-            .onEach {
-                logBuffer.log(
-                    TAG,
-                    LogLevel.INFO,
-                    { str1 = it },
-                    { "Resulting carrier text = $str1" }
-                )
-            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLog,
+                columnPrefix = "vm",
+                columnName = COL_CARRIER_TEXT,
+                initialValue = null,
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
     companion object {
         private const val TAG = "DeviceBasedSatelliteViewModel"
         private val DELAY_DURATION = 10.seconds
 
-        const val COL_VISIBLE_CONDITION = "visCondition"
+        const val COL_VISIBLE_FOR_OOS = "visibleForOos"
         const val COL_VISIBLE = "visible"
+        const val COL_CARRIER_TEXT = "carrierText"
     }
 }
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..87d0e64 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
@@ -18,27 +18,16 @@
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
-import android.annotation.IdRes
-import android.content.res.ColorStateList
-import android.graphics.drawable.GradientDrawable
 import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.ImageView
-import android.widget.TextView
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.Flags
-import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.repeatWhenAttached
 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.ui.binder.ChipChronometerBinder
+import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
-import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
 import javax.inject.Inject
@@ -92,62 +81,22 @@
                 }
 
                 if (Flags.statusBarScreenSharingChips()) {
-                    val chipView: View = view.requireViewById(R.id.ongoing_activity_chip)
-                    val chipContext = chipView.context
-                    val chipDefaultIconView: ImageView =
-                        chipView.requireViewById(R.id.ongoing_activity_chip_icon)
-                    val chipTimeView: ChipChronometer =
-                        chipView.requireViewById(R.id.ongoing_activity_chip_time)
-                    val chipTextView: TextView =
-                        chipView.requireViewById(R.id.ongoing_activity_chip_text)
-                    val chipBackgroundView =
-                        chipView.requireViewById<ChipBackgroundContainer>(
-                            R.id.ongoing_activity_chip_background
-                        )
+                    val primaryChipView: View =
+                        view.requireViewById(R.id.ongoing_activity_chip_primary)
                     launch {
-                        viewModel.ongoingActivityChip.collect { chipModel ->
-                            when (chipModel) {
-                                is OngoingActivityChipModel.Shown -> {
-                                    // Data
-                                    setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView)
-                                    setChipMainContent(chipModel, chipTextView, chipTimeView)
-                                    chipView.setOnClickListener(chipModel.onClickListener)
-                                    updateChipPadding(
-                                        chipModel,
-                                        chipBackgroundView,
-                                        chipTextView,
-                                        chipTimeView,
-                                    )
-
-                                    // Accessibility
-                                    setChipAccessibility(chipModel, chipView, chipBackgroundView)
-
-                                    // 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 =
-                                        chipModel.colors.background(chipContext)
-
-                                    // Notify listeners
+                        viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
+                            OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView)
+                            when (primaryChipModel) {
+                                is OngoingActivityChipModel.Shown ->
                                     listener.onOngoingActivityStatusChanged(
                                         hasOngoingActivity = true,
                                         shouldAnimate = true,
                                     )
-                                }
-                                is OngoingActivityChipModel.Hidden -> {
-                                    // The Chronometer should be stopped to prevent leaks -- see
-                                    // b/192243808 and [Chronometer.start].
-                                    chipTimeView.stop()
+                                is OngoingActivityChipModel.Hidden ->
                                     listener.onOngoingActivityStatusChanged(
                                         hasOngoingActivity = false,
-                                        shouldAnimate = chipModel.shouldAnimate,
+                                        shouldAnimate = primaryChipModel.shouldAnimate,
                                     )
-                                }
                             }
                         }
                     }
@@ -164,214 +113,6 @@
         }
     }
 
-    private fun setChipIcon(
-        chipModel: OngoingActivityChipModel.Shown,
-        backgroundView: ChipBackgroundContainer,
-        defaultIconView: ImageView,
-    ) {
-        // Always remove any previously set custom icon. If we have a new custom icon, we'll re-add
-        // it.
-        backgroundView.removeView(backgroundView.getCustomIconView())
-
-        when (val icon = chipModel.icon) {
-            null -> {
-                defaultIconView.visibility = View.GONE
-            }
-            is OngoingActivityChipModel.ChipIcon.Basic -> {
-                IconViewBinder.bind(icon.impl, defaultIconView)
-                defaultIconView.visibility = View.VISIBLE
-            }
-            is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
-                // Hide the default icon since we'll show this custom icon instead.
-                defaultIconView.visibility = View.GONE
-
-                // Add the new custom icon:
-                // 1. Set up the right visual params.
-                val iconView = icon.impl
-                with(iconView) {
-                    id = CUSTOM_ICON_VIEW_ID
-                    // TODO(b/354930838): Update the content description to not include "phone" and
-                    // maybe include the app name.
-                    contentDescription =
-                        context.resources.getString(R.string.ongoing_phone_call_content_description)
-                }
-
-                // 2. If we just reinflated the view, we may need to detach the icon view from the
-                // old chip before we reattach it to the new one.
-                // See also: NotificationIconContainerViewBinder#bindIcons.
-                val currentParent = iconView.parent as? ViewGroup
-                if (currentParent != null && currentParent != backgroundView) {
-                    currentParent.removeView(iconView)
-                    currentParent.removeTransientView(iconView)
-                }
-
-                // 3: Add the icon as the starting view.
-                backgroundView.addView(
-                    iconView,
-                    /* index= */ 0,
-                    generateCustomIconLayoutParams(iconView),
-                )
-            }
-        }
-    }
-
-    private fun View.getCustomIconView(): StatusBarIconView? {
-        return this.findViewById(CUSTOM_ICON_VIEW_ID)
-    }
-
-    private fun generateCustomIconLayoutParams(iconView: ImageView): FrameLayout.LayoutParams {
-        val customIconSize =
-            iconView.context.resources.getDimensionPixelSize(
-                R.dimen.ongoing_activity_chip_embedded_padding_icon_size
-            )
-        return FrameLayout.LayoutParams(customIconSize, customIconSize)
-    }
-
-    private fun setChipMainContent(
-        chipModel: OngoingActivityChipModel.Shown,
-        chipTextView: TextView,
-        chipTimeView: ChipChronometer,
-    ) {
-        when (chipModel) {
-            is OngoingActivityChipModel.Shown.Countdown -> {
-                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
-            }
-            is OngoingActivityChipModel.Shown.Timer -> {
-                ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView)
-                chipTimeView.visibility = View.VISIBLE
-
-                chipTextView.visibility = View.GONE
-            }
-            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
-            }
-        }
-    }
-
-    private fun updateChipPadding(
-        chipModel: OngoingActivityChipModel.Shown,
-        backgroundView: View,
-        chipTextView: TextView,
-        chipTimeView: ChipChronometer,
-    ) {
-        if (chipModel.icon != null) {
-            if (chipModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarView) {
-                // If the icon is a custom [StatusBarIconView], then it should've come from
-                // `Notification.smallIcon`, which is required to embed its own paddings. We need to
-                // adjust the other paddings to make everything look good :)
-                backgroundView.setBackgroundPaddingForEmbeddedPaddingIcon()
-                chipTextView.setTextPaddingForEmbeddedPaddingIcon()
-                chipTimeView.setTextPaddingForEmbeddedPaddingIcon()
-            } else {
-                backgroundView.setBackgroundPaddingForNormalIcon()
-                chipTextView.setTextPaddingForNormalIcon()
-                chipTimeView.setTextPaddingForNormalIcon()
-            }
-        } else {
-            backgroundView.setBackgroundPaddingForNoIcon()
-            chipTextView.setTextPaddingForNoIcon()
-            chipTimeView.setTextPaddingForNoIcon()
-        }
-    }
-
-    private fun View.setTextPaddingForEmbeddedPaddingIcon() {
-        val newPaddingEnd =
-            context.resources.getDimensionPixelSize(
-                R.dimen.ongoing_activity_chip_text_end_padding_for_embedded_padding_icon
-            )
-        setPaddingRelative(
-            // The icon should embed enough padding between the icon and time view.
-            /* start= */ 0,
-            this.paddingTop,
-            newPaddingEnd,
-            this.paddingBottom,
-        )
-    }
-
-    private fun View.setTextPaddingForNormalIcon() {
-        this.setPaddingRelative(
-            this.context.resources.getDimensionPixelSize(
-                R.dimen.ongoing_activity_chip_icon_text_padding
-            ),
-            paddingTop,
-            // The background view will contain the right end padding.
-            /* end= */ 0,
-            paddingBottom,
-        )
-    }
-
-    private fun View.setTextPaddingForNoIcon() {
-        // The background view will have even start & end paddings, so we don't want the text view
-        // to add any additional padding.
-        this.setPaddingRelative(/* start= */ 0, paddingTop, /* end= */ 0, paddingBottom)
-    }
-
-    private fun View.setBackgroundPaddingForEmbeddedPaddingIcon() {
-        val sidePadding =
-            context.resources.getDimensionPixelSize(
-                R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon
-            )
-        setPaddingRelative(
-            sidePadding,
-            paddingTop,
-            sidePadding,
-            paddingBottom,
-        )
-    }
-
-    private fun View.setBackgroundPaddingForNormalIcon() {
-        val sidePadding =
-            context.resources.getDimensionPixelSize(R.dimen.ongoing_activity_chip_side_padding)
-        setPaddingRelative(
-            sidePadding,
-            paddingTop,
-            sidePadding,
-            paddingBottom,
-        )
-    }
-
-    private fun View.setBackgroundPaddingForNoIcon() {
-        // The padding for the normal icon is also appropriate for no icon.
-        setBackgroundPaddingForNormalIcon()
-    }
-
-    private fun setChipAccessibility(
-        chipModel: OngoingActivityChipModel.Shown,
-        chipView: View,
-        chipBackgroundView: View,
-    ) {
-        when (chipModel) {
-            is OngoingActivityChipModel.Shown.Countdown -> {
-                // Set as assertive so talkback will announce the countdown
-                chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
-            }
-            is OngoingActivityChipModel.Shown.Timer,
-            is OngoingActivityChipModel.Shown.IconOnly -> {
-                chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE
-            }
-        }
-        // Clickable chips need to be a minimum size for accessibility purposes, but let
-        // non-clickable chips be smaller.
-        if (chipModel.onClickListener != null) {
-            chipBackgroundView.minimumWidth =
-                chipBackgroundView.context.resources.getDimensionPixelSize(
-                    R.dimen.min_clickable_item_size
-                )
-        } else {
-            chipBackgroundView.minimumWidth = 0
-        }
-    }
-
     private fun animateLightsOutView(view: View, visible: Boolean) {
         view.animate().cancel()
 
@@ -401,10 +142,6 @@
             )
             .start()
     }
-
-    companion object {
-        @IdRes private val CUSTOM_ICON_VIEW_ID = R.id.ongoing_activity_chip_custom_icon
-    }
 }
 
 /** Listener for various events that may affect the status bar's visibility. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
index d6c3834..5474231 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
@@ -64,8 +64,11 @@
     /** Emits whenever a transition from lockscreen to dream has started. */
     val transitionFromLockscreenToDreamStartedEvent: Flow<Unit>
 
-    /** The ongoing activity chip that should be shown on the left-hand side of the status bar. */
-    val ongoingActivityChip: StateFlow<OngoingActivityChipModel>
+    /**
+     * The ongoing activity chip that should be primarily shown on the left-hand side of the status
+     * bar. If there are multiple ongoing activity chips, this one should take priority.
+     */
+    val primaryOngoingActivityChip: StateFlow<OngoingActivityChipModel>
 
     /**
      * True if the current scene can show the home status bar (aka this status bar), and false if
@@ -108,7 +111,7 @@
             .filter { it.transitionState == TransitionState.STARTED }
             .map {}
 
-    override val ongoingActivityChip = ongoingActivityChipsViewModel.chip
+    override val primaryOngoingActivityChip = ongoingActivityChipsViewModel.primaryChip
 
     override val isHomeStatusBarAllowedByScene: StateFlow<Boolean> =
         combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 885abca..7df4b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -29,8 +29,6 @@
 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.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.table.TableLogBuffer
@@ -75,7 +73,6 @@
 class WifiRepositoryImpl
 @Inject
 constructor(
-    featureFlags: FeatureFlags,
     @Application private val scope: CoroutineScope,
     @Main private val mainExecutor: Executor,
     @Background private val bgDispatcher: CoroutineDispatcher,
@@ -90,8 +87,6 @@
             mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
         }
 
-    private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
-
     private var wifiPickerTracker: WifiPickerTracker? = null
 
     private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
@@ -109,16 +104,11 @@
                             val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
                             logOnWifiEntriesChanged(connectedEntry)
 
+                            val activeNetworks = wifiPickerTracker?.activeWifiEntries ?: emptyList()
                             val secondaryNetworks =
-                                if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
-                                    val activeNetworks =
-                                        wifiPickerTracker?.activeWifiEntries ?: emptyList()
-                                    activeNetworks
-                                        .filter { it != connectedEntry && !it.isPrimaryNetwork }
-                                        .map { it.toWifiNetworkModel() }
-                                } else {
-                                    emptyList()
-                                }
+                                activeNetworks
+                                    .filter { it != connectedEntry && !it.isPrimaryNetwork }
+                                    .map { it.toWifiNetworkModel() }
 
                             // [WifiPickerTracker.connectedWifiEntry] will return the same instance
                             // but with updated internals. For example, when its validation status
@@ -130,7 +120,8 @@
                             // into our internal model immediately. [toWifiNetworkModel] always
                             // returns a new instance, so the flow is guaranteed to emit.
                             send(
-                                newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
+                                newPrimaryNetwork =
+                                    connectedEntry?.toPrimaryWifiNetworkModel()
                                         ?: WIFI_NETWORK_DEFAULT,
                                 newSecondaryNetworks = secondaryNetworks,
                                 newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
@@ -277,7 +268,7 @@
         }
 
         val hotspotDeviceType =
-            if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
+            if (this is HotspotNetworkEntry) {
                 this.deviceType.toHotspotDeviceType()
             } else {
                 WifiNetworkModel.HotspotDeviceType.NONE
@@ -408,7 +399,6 @@
     class Factory
     @Inject
     constructor(
-        private val featureFlags: FeatureFlags,
         @Application private val scope: CoroutineScope,
         @Main private val mainExecutor: Executor,
         @Background private val bgDispatcher: CoroutineDispatcher,
@@ -418,7 +408,6 @@
     ) {
         fun create(wifiManager: WifiManager): WifiRepositoryImpl {
             return WifiRepositoryImpl(
-                featureFlags,
                 scope,
                 mainExecutor,
                 bgDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 21ec14f..591d7af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -403,7 +403,7 @@
                     tileSpec = TileSpec.create(DND_TILE_SPEC),
                     uiConfig =
                         QSTileUIConfig.Resource(
-                            iconRes = R.drawable.qs_dnd_icon_off,
+                            iconRes = com.android.internal.R.drawable.ic_zen_priority_modes,
                             labelRes = R.string.quick_settings_modes_label,
                         ),
                     instanceId = uiEventLogger.getNewInstanceId(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 71bcdfcb..b81af86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -22,8 +22,12 @@
 
 import com.android.internal.R;
 import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import com.android.settingslib.notification.modes.ZenIconLoader;
+import com.android.systemui.common.ui.GlobalConfig;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogBufferFactory;
 import com.android.systemui.settings.UserTracker;
@@ -79,6 +83,7 @@
 import dagger.Provides;
 
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
 
 import javax.inject.Named;
 
@@ -100,9 +105,12 @@
     @Binds
     CastController provideCastController(CastControllerImpl controllerImpl);
 
-    /** */
+    /**
+     * @deprecated: unscoped configuration controller shouldn't be injected as it might lead to
+     * wrong updates in case of secondary displays.
+     */
     @Binds
-    ConfigurationController bindConfigurationController(ConfigurationControllerImpl impl);
+    ConfigurationController bindConfigurationController(@GlobalConfig ConfigurationController impl);
 
     /** */
     @Binds
@@ -178,6 +186,15 @@
             DevicePostureControllerImpl devicePostureControllerImpl);
 
     /** */
+    @Provides
+    @SysUISingleton
+    @GlobalConfig
+    static ConfigurationController provideGlobalConfigurationController(
+            @Application Context context, ConfigurationControllerImpl.Factory factory) {
+        return factory.create(context);
+    }
+
+    /** */
     @SysUISingleton
     @Provides
     static AccessPointControllerImpl  provideAccessPointControllerImpl(
@@ -236,4 +253,12 @@
     static LogBuffer provideCastControllerLog(LogBufferFactory factory) {
         return factory.create("CastControllerLog", 50);
     }
+
+    /** Provides a {@link ZenIconLoader} that fetches icons in a background thread. */
+    @Provides
+    @SysUISingleton
+    static ZenIconLoader provideZenIconLoader(
+            @UiBackground ExecutorService backgroundExecutorService) {
+        return new ZenIconLoader(backgroundExecutorService);
+    }
 }
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 7586133..dbeaa59 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
@@ -23,16 +23,20 @@
 import android.util.Log
 import androidx.concurrent.futures.await
 import com.android.settingslib.notification.data.repository.ZenModeRepository
+import com.android.settingslib.notification.modes.ZenIcon
 import com.android.settingslib.notification.modes.ZenIconLoader
 import com.android.settingslib.notification.modes.ZenMode
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
+import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
+import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
 import java.time.Duration
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
 /**
@@ -42,11 +46,12 @@
 class ZenModeInteractor
 @Inject
 constructor(
+    private val context: Context,
     private val zenModeRepository: ZenModeRepository,
     private val notificationSettingsRepository: NotificationSettingsRepository,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val iconLoader: ZenIconLoader,
 ) {
-    private val iconLoader: ZenIconLoader = ZenIconLoader.getInstance()
-
     val isZenModeEnabled: Flow<Boolean> =
         zenModeRepository.globalZenMode
             .map {
@@ -75,27 +80,29 @@
 
     val modes: Flow<List<ZenMode>> = zenModeRepository.modes
 
-    val activeModes: Flow<List<ZenMode>> =
-        modes.map { modes -> modes.filter { mode -> mode.isActive } }.distinctUntilChanged()
+    /** Flow returning the currently active mode(s), if any. */
+    val activeModes: Flow<ActiveZenModes> =
+        modes
+            .map { modes -> buildActiveZenModes(modes) }
+            .flowOn(bgDispatcher)
+            .distinctUntilChanged()
 
-    /**
-     * Given the list of modes (which may include zero or more currently active modes), returns an
-     * icon representing the active mode, if any (or, if multiple modes are active, to the most
-     * prioritized one). This icon is suitable for use in the status bar or lockscreen (uses the
-     * 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 modes
-            .sortedWith(ZenMode.PRIORITIZING_COMPARATOR)
-            .firstOrNull { it.isActive }
-            ?.getLockscreenIcon(context, iconLoader)
-            ?.await()
-            ?.asIcon()
+    suspend fun getActiveModes() = buildActiveZenModes(zenModeRepository.getModes())
+
+    private suspend fun buildActiveZenModes(modes: List<ZenMode>): ActiveZenModes {
+        val activeModesList =
+            modes.filter { mode -> mode.isActive }.sortedWith(ZenMode.PRIORITIZING_COMPARATOR)
+        val mainActiveMode =
+            activeModesList.firstOrNull()?.let { ZenModeInfo(it.name, getModeIcon(it)) }
+
+        return ActiveZenModes(activeModesList.map { m -> m.name }, mainActiveMode)
     }
 
-    suspend fun getModeIcon(context: Context, mode: ZenMode): Icon {
-        return mode.getIcon(context, iconLoader).await().asIcon()
+    val mainActiveMode: Flow<ZenModeInfo?> =
+        activeModes.map { a -> a.mainMode }.distinctUntilChanged()
+
+    suspend fun getModeIcon(mode: ZenMode): ZenIcon {
+        return iconLoader.getIcon(context, mode).await()
     }
 
     fun activateMode(zenMode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ActiveZenModes.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ActiveZenModes.kt
new file mode 100644
index 0000000..569e517
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ActiveZenModes.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.policy.domain.model
+
+import com.android.settingslib.notification.modes.ZenMode
+
+/**
+ * Represents the list of [ZenMode] instances that are currently active.
+ *
+ * @property modeNames Names of all the active modes, sorted by their priority.
+ * @property mainMode The most prioritized active mode, if any modes active. Guaranteed to be
+ *   non-null if [modeNames] is not empty.
+ */
+data class ActiveZenModes(val modeNames: List<String>, val mainMode: ZenModeInfo?) {
+    fun isAnyActive(): Boolean = modeNames.isNotEmpty()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt
index d60f14c..5004f4c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume
+package com.android.systemui.statusbar.policy.domain.model
 
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.settingslib.notification.modes.ZenIcon
+import com.android.settingslib.notification.modes.ZenMode
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+/** Name and icon of a [ZenMode] */
+data class ZenModeInfo(val name: String, val icon: ZenIcon)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 3fffd9f..af93880 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy.ui.dialog.composable
 
+import androidx.compose.animation.animateColorAsState
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.combinedClickable
 import androidx.compose.foundation.layout.Arrangement
@@ -30,9 +31,15 @@
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import com.android.systemui.common.ui.compose.Icon
@@ -40,12 +47,16 @@
 
 @Composable
 fun ModeTile(viewModel: ModeTileViewModel) {
-    val tileColor =
-        if (viewModel.enabled) MaterialTheme.colorScheme.primary
-        else MaterialTheme.colorScheme.surfaceVariant
-    val contentColor =
-        if (viewModel.enabled) MaterialTheme.colorScheme.onPrimary
-        else MaterialTheme.colorScheme.onSurfaceVariant
+    val tileColor: Color by
+        animateColorAsState(
+            if (viewModel.enabled) MaterialTheme.colorScheme.primary
+            else MaterialTheme.colorScheme.surfaceVariant
+        )
+    val contentColor: Color by
+        animateColorAsState(
+            if (viewModel.enabled) MaterialTheme.colorScheme.onPrimary
+            else MaterialTheme.colorScheme.onSurfaceVariant
+        )
 
     CompositionLocalProvider(LocalContentColor provides contentColor) {
         Surface(
@@ -56,9 +67,11 @@
                 modifier =
                     Modifier.combinedClickable(
                             onClick = viewModel.onClick,
-                            onLongClick = viewModel.onLongClick
+                            onLongClick = viewModel.onLongClick,
+                            onLongClickLabel = viewModel.onLongClickLabel
                         )
-                        .padding(20.dp),
+                        .padding(20.dp)
+                        .semantics { stateDescription = viewModel.stateDescription },
                 verticalAlignment = Alignment.CenterVertically,
                 horizontalArrangement =
                     Arrangement.spacedBy(
@@ -76,7 +89,12 @@
                     Text(
                         viewModel.subtext,
                         fontWeight = FontWeight.W400,
-                        modifier = Modifier.tileMarquee().testTag("state")
+                        modifier =
+                            Modifier.tileMarquee()
+                                .testTag(if (viewModel.enabled) "stateOn" else "stateOff")
+                                .clearAndSetSemantics {
+                                    contentDescription = viewModel.subtextDescription
+                                }
                     )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
index 7c1cb6a..abd2453 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
@@ -28,7 +28,10 @@
     val icon: Icon,
     val text: String,
     val subtext: String,
+    val subtextDescription: String, // version of subtext without "on"/"off" for screen readers
     val enabled: Boolean,
+    val stateDescription: String, // "on"/"off" state of the tile, for screen readers
     val onClick: () -> Unit,
     val onLongClick: () -> Unit,
+    val onLongClickLabel: String, // for screen readers
 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 02b5e49..6764839c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -23,6 +23,7 @@
 import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
 import com.android.settingslib.notification.modes.EnableZenModeDialog
 import com.android.settingslib.notification.modes.ZenMode
+import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger
@@ -88,10 +89,15 @@
                 modesList.map { mode ->
                     ModeTileViewModel(
                         id = mode.id,
-                        icon = zenModeInteractor.getModeIcon(context, mode),
+                        icon = zenModeInteractor.getModeIcon(mode).drawable().asIcon(),
                         text = mode.name,
                         subtext = getTileSubtext(mode),
+                        subtextDescription = getModeDescription(mode) ?: "",
                         enabled = mode.isActive,
+                        stateDescription =
+                            context.getString(
+                                if (mode.isActive) R.string.zen_mode_on else R.string.zen_mode_off
+                            ),
                         onClick = {
                             if (!mode.rule.isEnabled) {
                                 openSettings(mode)
@@ -112,7 +118,9 @@
                                 }
                             }
                         },
-                        onLongClick = { openSettings(mode) }
+                        onLongClick = { openSettings(mode) },
+                        onLongClickLabel =
+                            context.resources.getString(R.string.accessibility_long_click_tile)
                     )
                 }
             }
@@ -127,23 +135,36 @@
         dialogDelegate.launchFromDialog(intent)
     }
 
-    private fun getTileSubtext(mode: ZenMode): String {
+    /**
+     * Returns a description of the mode, which is:
+     *   * a prompt to set up the mode if it is not enabled
+     *   * if it cannot be manually activated, text that says so
+     *   * otherwise, the trigger description of the mode if it exists...
+     *   * ...or null if it doesn't
+     *
+     * This description is used directly for the content description of a mode tile for screen
+     * readers, and for the tile subtext will be augmented with the current status of the mode.
+     */
+    private fun getModeDescription(mode: ZenMode): String? {
         if (!mode.rule.isEnabled) {
             return context.resources.getString(R.string.zen_mode_set_up)
         }
         if (!mode.rule.isManualInvocationAllowed && !mode.isActive) {
             return context.resources.getString(R.string.zen_mode_no_manual_invocation)
         }
+        return mode.getDynamicDescription(context)
+    }
 
-        val modeSubtext = mode.getDynamicDescription(context)
+    private fun getTileSubtext(mode: ZenMode): String {
+        val modeDescription = getModeDescription(mode)
         return if (mode.isActive) {
-            if (modeSubtext != null) {
-                context.getString(R.string.zen_mode_on_with_details, modeSubtext)
+            if (modeDescription != null) {
+                context.getString(R.string.zen_mode_on_with_details, modeDescription)
             } else {
                 context.getString(R.string.zen_mode_on)
             }
         } else {
-            modeSubtext ?: context.getString(R.string.zen_mode_off)
+            modeDescription ?: context.getString(R.string.zen_mode_off)
         }
     }
 
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/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 1c8041f..a3b1867 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.touchpad.tutorial.ui.composable
 
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import com.airbnb.lottie.compose.rememberLottieDynamicProperties
@@ -67,7 +66,6 @@
     val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
     val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
     val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim
-    val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
     val dynamicProperties =
         rememberLottieDynamicProperties(
             rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
@@ -76,10 +74,9 @@
             rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
         )
     val screenColors =
-        remember(onTertiaryFixed, surfaceContainer, tertiaryFixedDim, dynamicProperties) {
+        remember(dynamicProperties) {
             TutorialScreenConfig.Colors(
                 background = onTertiaryFixed,
-                successBackground = surfaceContainer,
                 title = tertiaryFixedDim,
                 animationColors = dynamicProperties,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 0a6283a..d4eb0cd 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.touchpad.tutorial.ui.composable
 
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import com.airbnb.lottie.compose.rememberLottieDynamicProperties
@@ -66,7 +65,6 @@
     val primaryFixedDim = LocalAndroidColorScheme.current.primaryFixedDim
     val onPrimaryFixed = LocalAndroidColorScheme.current.onPrimaryFixed
     val onPrimaryFixedVariant = LocalAndroidColorScheme.current.onPrimaryFixedVariant
-    val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
     val dynamicProperties =
         rememberLottieDynamicProperties(
             rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
@@ -74,10 +72,9 @@
             rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant)
         )
     val screenColors =
-        remember(surfaceContainer, dynamicProperties) {
+        remember(dynamicProperties) {
             TutorialScreenConfig.Colors(
                 background = onPrimaryFixed,
-                successBackground = surfaceContainer,
                 title = primaryFixedDim,
                 animationColors = dynamicProperties,
             )
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/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
index ecf1165..70774f13 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java
@@ -28,6 +28,7 @@
 import dagger.Provides;
 
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 import javax.inject.Singleton;
@@ -81,6 +82,18 @@
     @Singleton
     @UiBackground
     public static Executor provideUiBackgroundExecutor() {
+        return provideUiBackgroundExecutorService();
+    }
+
+    /**
+     * Provide an ExecutorService specifically for running UI operations on a separate thread.
+     *
+     * <p>Keep submitted runnables short and to the point, just as with any other UI code.
+     */
+    @Provides
+    @Singleton
+    @UiBackground
+    public static ExecutorService provideUiBackgroundExecutorService() {
         return Executors.newSingleThreadExecutor();
     }
 
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 28ac2c0..64e056d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -28,9 +28,13 @@
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlinx.coroutines.CoroutineScope
+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. */
@@ -57,12 +61,23 @@
     ): 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)
+    }
 }
 
 /**
  * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
  * [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners
- * during onViewAttached() and removing during onViewRemoved()
+ * during onViewAttached() and removing during onViewRemoved().
+ *
+ * @return a disposable handle in order to cancel the flow in the future.
  */
 @JvmOverloads
 fun <T> collectFlow(
@@ -71,8 +86,8 @@
     consumer: Consumer<T>,
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     state: Lifecycle.State = Lifecycle.State.CREATED,
-) {
-    view.repeatWhenAttached(coroutineContext) {
+): DisposableHandle {
+    return view.repeatWhenAttached(coroutineContext) {
         repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
     }
 }
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..8934d8f 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:
@@ -1270,6 +1280,8 @@
 
     private final class Receiver extends BroadcastReceiver {
 
+        private static final int STREAM_UNKNOWN = -1;
+
         public void init() {
             final IntentFilter filter = new IntentFilter();
             filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
@@ -1291,30 +1303,39 @@
             final String action = intent.getAction();
             boolean changed = false;
             if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
-                final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+                final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                        STREAM_UNKNOWN);
                 final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
                 final int oldLevel = intent
                         .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
                 if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
                         + " level=" + level + " oldLevel=" + oldLevel);
-                changed = updateStreamLevelW(stream, level);
+                if (stream != STREAM_UNKNOWN) {
+                    changed = updateStreamLevelW(stream, level);
+                }
             } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
-                final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+                final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                        STREAM_UNKNOWN);
                 final int devices = intent
                         .getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1);
                 final int oldDevices = intent
                         .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1);
                 if (D.BUG) Log.d(TAG, "onReceive STREAM_DEVICES_CHANGED_ACTION stream="
                         + stream + " devices=" + devices + " oldDevices=" + oldDevices);
-                changed = checkRoutedToBluetoothW(stream);
-                changed |= onVolumeChangedW(stream, 0);
+                if (stream != STREAM_UNKNOWN) {
+                    changed |= checkRoutedToBluetoothW(stream);
+                    changed |= onVolumeChangedW(stream, 0);
+                }
             } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
-                final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+                final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                        STREAM_UNKNOWN);
                 final boolean muted = intent
                         .getBooleanExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
                 if (D.BUG) Log.d(TAG, "onReceive STREAM_MUTE_CHANGED_ACTION stream=" + stream
                         + " muted=" + muted);
-                changed = updateStreamMuteW(stream, muted);
+                if (stream != STREAM_UNKNOWN) {
+                    changed = updateStreamMuteW(stream, muted);
+                }
             } else if (action.equals(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)) {
                 if (D.BUG) Log.d(TAG, "onReceive ACTION_EFFECTS_SUPPRESSOR_CHANGED");
                 changed = updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
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/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index 4f77cd0..73728e6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -75,7 +75,7 @@
             }
             .map { it ?: AudioOutputDevice.Unknown }
             .flowOn(backgroundCoroutineContext)
-            .stateIn(scope, SharingStarted.Eagerly, AudioOutputDevice.Unknown)
+            .stateIn(scope, SharingStarted.Eagerly, AudioOutputDevice.Unavailable)
 
     private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice {
         if (
@@ -120,6 +120,11 @@
                     name = name,
                     icon = icon,
                 )
+            deviceType == MediaDeviceType.TYPE_CAST_DEVICE ->
+                AudioOutputDevice.Remote(
+                    name = name,
+                    icon = icon,
+                )
             else ->
                 AudioOutputDevice.BuiltIn(
                     name = name,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/model/AudioOutputDevice.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/model/AudioOutputDevice.kt
index ba0b082..0e4cac0b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/model/AudioOutputDevice.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/model/AudioOutputDevice.kt
@@ -31,6 +31,12 @@
         override val icon: Drawable?,
     ) : AudioOutputDevice
 
+    /** Models a cast audio output device. */
+    data class Remote(
+        override val name: String,
+        override val icon: Drawable?,
+    ) : AudioOutputDevice
+
     /** Models a wired audio output device. */
     data class Wired(
         override val name: String,
@@ -52,4 +58,16 @@
         override val icon: Drawable
             get() = error("Unsupported for unknown device")
     }
+
+    /**
+     * Models a state when current audio output device is not loaded yet or the system failed to
+     * load it.
+     */
+    data object Unavailable : AudioOutputDevice {
+        override val name: String
+            get() = error("Unsupported for unavailable device")
+
+        override val icon: Drawable
+            get() = error("Unsupported for unavailable device")
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
index a270d5f..f94cbda 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
@@ -74,34 +74,51 @@
             )
 
     private val currentAudioDevice: Flow<AudioOutputDevice> =
-        audioOutputInteractor.currentAudioDevice.filter { it !is AudioOutputDevice.Unknown }
+        audioOutputInteractor.currentAudioDevice.filter { it !is AudioOutputDevice.Unavailable }
 
+    /**
+     * Model for the Media Output component in the Volume Panel. It's guaranteed to have an
+     * available device if it's loaded.
+     */
     val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> =
-        audioModeInteractor.isOngoingCall
-            .flatMapLatest { isOngoingCall ->
-                audioSharingInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
-                    if (isOngoingCall) {
-                        currentAudioDevice.map {
-                            MediaOutputComponentModel.Calling(it, isInAudioSharing)
-                        }
-                    } else {
-                        combine(sessionWithPlaybackState.filterData(), currentAudioDevice) {
-                            sessionWithPlaybackState,
-                            currentAudioDevice ->
-                            if (sessionWithPlaybackState == null) {
-                                MediaOutputComponentModel.Idle(currentAudioDevice, isInAudioSharing)
-                            } else {
-                                MediaOutputComponentModel.MediaSession(
-                                    sessionWithPlaybackState.session,
-                                    sessionWithPlaybackState.isPlaybackActive,
-                                    currentAudioDevice,
-                                    isInAudioSharing,
-                                )
-                            }
+        combine(
+                audioSharingInteractor.isInAudioSharing,
+                audioModeInteractor.isOngoingCall,
+                currentAudioDevice,
+            ) { isInAudioSharing, isOngoingCall, currentAudioDevice ->
+                if (isOngoingCall) {
+                    flowOf(
+                        MediaOutputComponentModel.Calling(
+                            device = currentAudioDevice,
+                            isInAudioSharing = isInAudioSharing,
+                            canOpenAudioSwitcher = false,
+                        )
+                    )
+                } else {
+                    sessionWithPlaybackState.filterData().map { sessionWithPlaybackState ->
+                        if (sessionWithPlaybackState == null) {
+                            MediaOutputComponentModel.Idle(
+                                device = currentAudioDevice,
+                                isInAudioSharing = isInAudioSharing,
+                                canOpenAudioSwitcher =
+                                    !isInAudioSharing &&
+                                        currentAudioDevice !is AudioOutputDevice.Unknown,
+                            )
+                        } else {
+                            MediaOutputComponentModel.MediaSession(
+                                session = sessionWithPlaybackState.session,
+                                isPlaybackActive = sessionWithPlaybackState.isPlaybackActive,
+                                device = currentAudioDevice,
+                                isInAudioSharing = isInAudioSharing,
+                                canOpenAudioSwitcher =
+                                    !isInAudioSharing &&
+                                        currentAudioDevice !is AudioOutputDevice.Unknown,
+                            )
                         }
                     }
                 }
             }
+            .flatMapLatest { it }
             .wrapInResult()
             .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index 31a8977..aa07cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -179,9 +179,7 @@
         return MediaDeviceSession(
             packageName = packageName,
             sessionToken = sessionToken,
-            canAdjustVolume =
-                playbackInfo != null &&
-                    playbackInfo?.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED,
+            canAdjustVolume = playbackInfo.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED,
             appLabel = getApplicationLabel(packageName) ?: return null
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
index 220fb2b..6588b44 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
@@ -24,11 +24,13 @@
 
     val device: AudioOutputDevice
     val isInAudioSharing: Boolean
+    val canOpenAudioSwitcher: Boolean
 
     /** There is an ongoing call on the device. */
     data class Calling(
         override val device: AudioOutputDevice,
         override val isInAudioSharing: Boolean,
+        override val canOpenAudioSwitcher: Boolean,
     ) : MediaOutputComponentModel
 
     /** There is media playing on the device. */
@@ -37,11 +39,13 @@
         val isPlaybackActive: Boolean,
         override val device: AudioOutputDevice,
         override val isInAudioSharing: Boolean,
+        override val canOpenAudioSwitcher: Boolean,
     ) : MediaOutputComponentModel
 
     /** There is nothing playing on the device. */
     data class Idle(
         override val device: AudioOutputDevice,
         override val isInAudioSharing: Boolean,
+        override val canOpenAudioSwitcher: Boolean,
     ) : MediaOutputComponentModel
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt
index 8ba672d..42f88b4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/ConnectedDeviceViewModel.kt
@@ -16,11 +16,15 @@
 
 package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
 
+import com.android.systemui.common.shared.model.Color
+
 /**
  * Models part of the Media Session Volume Panel component that displays connected device
  * information.
  */
 data class ConnectedDeviceViewModel(
     val label: CharSequence,
+    val labelColor: Color,
     val deviceName: CharSequence?,
+    val deviceNameColor: Color,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 36b42f2..e565de5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -75,12 +75,25 @@
                         }
                     }
                 ConnectedDeviceViewModel(
-                    label,
-                    if (mediaOutputModel.isInAudioSharing) {
-                        context.getString(R.string.audio_sharing_description)
-                    } else {
-                        mediaOutputModel.device.name
-                    },
+                    label = label,
+                    labelColor =
+                        Color.Attribute(com.android.internal.R.attr.materialColorOnSurfaceVariant),
+                    deviceName =
+                        if (mediaOutputModel.isInAudioSharing) {
+                            context.getString(R.string.audio_sharing_description)
+                        } else {
+                            mediaOutputModel.device
+                                .takeIf { it !is AudioOutputDevice.Unknown }
+                                ?.name ?: context.getString(R.string.media_seamless_other_device)
+                        },
+                    deviceNameColor =
+                        if (mediaOutputModel.canOpenAudioSwitcher) {
+                            Color.Attribute(com.android.internal.R.attr.materialColorOnSurface)
+                        } else {
+                            Color.Attribute(
+                                com.android.internal.R.attr.materialColorOnSurfaceVariant
+                            )
+                        },
                 )
             }
             .stateIn(
@@ -107,19 +120,39 @@
                     DeviceIconViewModel.IsPlaying(
                         icon = icon,
                         iconColor =
-                            Color.Attribute(com.android.internal.R.attr.materialColorSurface),
+                            if (mediaOutputModel.canOpenAudioSwitcher) {
+                                Color.Attribute(com.android.internal.R.attr.materialColorSurface)
+                            } else {
+                                Color.Attribute(
+                                    com.android.internal.R.attr.materialColorSurfaceContainerHighest
+                                )
+                            },
                         backgroundColor =
-                            Color.Attribute(com.android.internal.R.attr.materialColorSecondary),
+                            if (mediaOutputModel.canOpenAudioSwitcher) {
+                                Color.Attribute(com.android.internal.R.attr.materialColorSecondary)
+                            } else {
+                                Color.Attribute(com.android.internal.R.attr.materialColorOutline)
+                            },
                     )
                 } else {
                     DeviceIconViewModel.IsNotPlaying(
                         icon = icon,
                         iconColor =
-                            Color.Attribute(
-                                com.android.internal.R.attr.materialColorOnSurfaceVariant
-                            ),
+                            if (mediaOutputModel.canOpenAudioSwitcher) {
+                                Color.Attribute(
+                                    com.android.internal.R.attr.materialColorOnSurfaceVariant
+                                )
+                            } else {
+                                Color.Attribute(com.android.internal.R.attr.materialColorOutline)
+                            },
                         backgroundColor =
-                            Color.Attribute(com.android.internal.R.attr.materialColorSurface),
+                            if (mediaOutputModel.canOpenAudioSwitcher) {
+                                Color.Attribute(com.android.internal.R.attr.materialColorSurface)
+                            } else {
+                                Color.Attribute(
+                                    com.android.internal.R.attr.materialColorSurfaceContainerHighest
+                                )
+                            },
                     )
                 }
             }
@@ -132,7 +165,7 @@
     val enabled: StateFlow<Boolean> =
         mediaOutputComponentInteractor.mediaOutputModel
             .filterData()
-            .map { !it.isInAudioSharing }
+            .map { it.canOpenAudioSwitcher }
             .stateIn(
                 coroutineScope,
                 SharingStarted.Eagerly,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index cfcd6b1..56d0bce 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -63,13 +63,7 @@
     private val changes = MutableSharedFlow<Unit>()
     private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
         audioOutputInteractor.currentAudioDevice
-            .map { audioDevice ->
-                if (audioDevice is AudioOutputDevice.Unknown) {
-                    builtinSpeaker
-                } else {
-                    audioDevice.getAudioDeviceAttributes()
-                }
-            }
+            .map { audioDevice -> audioDevice.getAudioDeviceAttributes() }
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker)
 
     /**
@@ -185,7 +179,10 @@
                         .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
                 }
             }
-            else -> null
+            is AudioOutputDevice.Wired -> null
+            is AudioOutputDevice.Remote -> null
+            is AudioOutputDevice.Unknown -> builtinSpeaker
+            is AudioOutputDevice.Unavailable -> builtinSpeaker
         }
     }
 
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/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
index 4841c78..ea213cb 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
@@ -23,11 +23,19 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.pipeline.shared.TileSpec;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.QuickAccessWalletTile;
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig;
+import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy;
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig;
+import com.android.systemui.res.R;
 import com.android.systemui.wallet.controller.WalletContextualLocationsService;
 import com.android.systemui.wallet.ui.WalletActivity;
 
+import java.util.concurrent.Executor;
+
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
@@ -35,14 +43,14 @@
 import dagger.multibindings.IntoMap;
 import dagger.multibindings.StringKey;
 
-import java.util.concurrent.Executor;
-
 /**
  * Module for injecting classes in Wallet.
  */
 @Module
 public abstract class WalletModule {
 
+    public static final String WALLET_TILE_SPEC = "wallet";
+
     @Binds
     @IntoMap
     @ClassKey(WalletContextualLocationsService.class)
@@ -69,4 +77,21 @@
     @StringKey(QuickAccessWalletTile.TILE_SPEC)
     public abstract QSTileImpl<?> bindQuickAccessWalletTile(
             QuickAccessWalletTile quickAccessWalletTile);
+
+    @Provides
+    @IntoMap
+    @StringKey(WALLET_TILE_SPEC)
+    public static QSTileConfig provideQuickAccessWalletTileConfig(QsEventLogger uiEventLogger) {
+        TileSpec tileSpec = TileSpec.create(WALLET_TILE_SPEC);
+        return new QSTileConfig(
+                tileSpec,
+                new QSTileUIConfig.Resource(
+                        R.drawable.ic_wallet_lockscreen,
+                        R.string.wallet_title
+                ),
+                uiEventLogger.getNewInstanceId(),
+                tileSpec.getSpec(),
+                QSTilePolicy.NoRestrictions.INSTANCE
+        );
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 50efc21..5f6ad92 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -60,7 +60,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.onehanded.OneHanded;
@@ -70,6 +69,7 @@
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.sysui.ShellInterface;
 
diff --git a/packages/SystemUI/tests/Android.bp b/packages/SystemUI/tests/Android.bp
index 88939a2..f601387 100644
--- a/packages/SystemUI/tests/Android.bp
+++ b/packages/SystemUI/tests/Android.bp
@@ -50,3 +50,14 @@
     additional_manifests: ["AndroidManifest.xml"],
     manifest: "AndroidManifest-base.xml",
 }
+
+test_module_config {
+    name: "SystemUITests_systemui_accessibility",
+    base: "SystemUITests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.systemui.accessibility"],
+    exclude_annotations: [
+        "android.platform.test.annotations.Postsubmit",
+        "android.platform.test.annotations.FlakyTest",
+    ],
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index 347605d..43a78035 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
@@ -19,6 +19,7 @@
 import android.app.ActivityTaskManager
 import android.content.pm.PackageManager
 import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
 import android.telecom.TelecomManager
 import android.telephony.TelephonyManager
 import android.testing.TestableLooper
@@ -26,14 +27,20 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.haptics.msdl.FakeMSDLPlayer
+import com.android.systemui.haptics.msdl.msdlPlayer
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,6 +71,8 @@
     val fakeSystemClock = FakeSystemClock()
     val mainExecutor = FakeExecutor(fakeSystemClock)
     val backgroundExecutor = FakeExecutor(fakeSystemClock)
+    private val kosmos = testKosmos()
+    private val msdlPlayer: FakeMSDLPlayer = kosmos.msdlPlayer
 
     lateinit var underTest: EmergencyButtonController
 
@@ -84,6 +93,7 @@
                 mainExecutor,
                 backgroundExecutor,
                 mSelectedUserInteractor,
+                msdlPlayer,
             )
         context.setMockPackageManager(packageManager)
         Mockito.`when`(emergencyButton.context).thenReturn(context)
@@ -113,4 +123,13 @@
                 /* isSecure= */ eq(true)
             )
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    fun takeEmergencyCallAction_withMSDLFeedback_playsEmergencyButtonTokenAndNullAttributes() {
+        underTest.takeEmergencyCallAction()
+
+        assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.KEYPRESS_RETURN)
+        assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index e724c60..c0d8be3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -27,6 +29,7 @@
 import static org.mockito.Mockito.when;
 
 import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.KeyEvent;
@@ -43,9 +46,13 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.haptics.msdl.FakeMSDLPlayer;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
+import com.google.android.msdl.data.model.MSDLToken;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -85,7 +92,11 @@
     private FakeFeatureFlags mFeatureFlags;
     @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock
+    private UserActivityNotifier mUserActivityNotifier;
     private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
+    private KosmosJavaAdapter mKosmosJavaAdapter = new KosmosJavaAdapter(this);
+    private final FakeMSDLPlayer mMSDLPlayer = mKosmosJavaAdapter.getMsdlPlayer();
 
     @Before
     public void setup() {
@@ -108,7 +119,8 @@
         return new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
-                mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor) {
+                mEmergencyButtonController, mFeatureFlags, mSelectedUserInteractor, mMSDLPlayer,
+                mUserActivityNotifier) {
             @Override
             void resetState() {
             }
@@ -197,4 +209,32 @@
         verify(mAbsKeyInputView, never()).setPasswordEntryInputEnabled(true);
         verify(mAbsKeyInputView, never()).setPasswordEntryEnabled(true);
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    public void onPasswordChecked_withMSDLFeedback_withMatch_playsUnlockToken() {
+        mKeyguardAbsKeyInputViewController.onPasswordChecked(0, true, 100, true);
+        assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.UNLOCK);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    public void onPasswordChecked_withoutMSDLFeedback_withMatch_doesNotPlayToken() {
+        mKeyguardAbsKeyInputViewController.onPasswordChecked(0, true, 100, true);
+        assertThat(mMSDLPlayer.getLatestTokenPlayed()).isNull();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    public void onPasswordChecked_withMSDLFeedback_withoutMatch_playsFailureToken() {
+        mKeyguardAbsKeyInputViewController.onPasswordChecked(0, false, 100, true);
+        assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.FAILURE);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
+    public void onPasswordChecked_withoutMSDLFeedback_withoutMatch_doesNotPlayToken() {
+        mKeyguardAbsKeyInputViewController.onPasswordChecked(0, false, 100, true);
+        assertThat(mMSDLPlayer.getLatestTokenPlayed()).isNull();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 36d4d12..873bc2c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -103,6 +103,7 @@
     @Mock lateinit var deleteButton: NumPadButton
     @Mock lateinit var enterButton: View
     @Mock lateinit var uiEventLogger: UiEventLogger
+    @Mock lateinit var mUserActivityNotifier: UserActivityNotifier
 
     @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
 
@@ -149,7 +150,9 @@
             featureFlags,
             mSelectedUserInteractor,
             uiEventLogger,
-            keyguardKeyboardInteractor
+            keyguardKeyboardInteractor,
+            null,
+            mUserActivityNotifier
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 7151c42..f141a49 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -69,6 +69,7 @@
     @Mock
     private lateinit var keyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+    @Mock private lateinit var mUserActivityNotifier: UserActivityNotifier
     private val updateMonitorCallbackArgumentCaptor =
         ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
 
@@ -101,7 +102,9 @@
                 emergencyButtonController,
                 fakeFeatureFlags,
                 mSelectedUserInteractor,
-                keyguardKeyboardInteractor
+                keyguardKeyboardInteractor,
+                null,
+                mUserActivityNotifier
             )
         underTest.init()
         underTest.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index acae913..a03c839 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -63,6 +63,7 @@
     @Mock
     private lateinit var keyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+    @Mock private lateinit var mUserActivityNotifier: UserActivityNotifier
 
     @Before
     fun setup() {
@@ -96,7 +97,9 @@
                 emergencyButtonController,
                 fakeFeatureFlags,
                 mSelectedUserInteractor,
-                keyguardKeyboardInteractor
+                keyguardKeyboardInteractor,
+                null,
+                mUserActivityNotifier
             )
         underTest.init()
     }
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/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 113a8c0..5e37d4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.hardware.display.DisplayManager;
+import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.TestableLooper;
@@ -80,6 +81,7 @@
     private AccessibilityManager mAccessibilityManager;
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private AccessibilityFloatingMenuController mController;
+    private TestableLooper mTestableLooper;
     @Mock
     private AccessibilityButtonTargetsObserver mTargetsObserver;
     @Mock
@@ -108,6 +110,7 @@
         mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager,
                 mLazyViewCapture, /* isViewCaptureEnabled= */ false);
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+        mTestableLooper = TestableLooper.get(this);
 
         when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
                 .thenReturn(Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
@@ -231,7 +234,8 @@
         mKeyguardCallback.onKeyguardVisibilityChanged(false);
 
         mKeyguardCallback.onUserSwitching(fakeUserId);
-        mKeyguardCallback.onUserSwitchComplete(fakeUserId);
+        mController.mUserInitializationCompleteCallback.onUserInitializationComplete(1);
+        mTestableLooper.processAllMessages();
 
         assertThat(mController.mFloatingMenu).isNotNull();
     }
@@ -346,7 +350,8 @@
                 new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
                         viewCaptureAwareWindowManager, displayManager, mAccessibilityManager,
                         mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor, mSecureSettings,
-                        displayTracker, mNavigationModeController);
+                        displayTracker, mNavigationModeController, new Handler(
+                                mTestableLooper.getLooper()));
         controller.init();
 
         return controller;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 5600b87..aa8c6b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -57,6 +57,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.log.LogBufferHelperKt;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.display.DisplayHelper;
@@ -153,7 +154,8 @@
             when(mWindowManager.getMaximumWindowMetrics()).thenReturn(mWindowMetrics);
             mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor, mLifecycleRegistry,
                     mInputFactory, mDisplayHelper, mKosmos.getConfigurationInteractor(),
-                    handlers, mIWindowManager,  0);
+                    handlers, mIWindowManager, 0, "TouchMonitorTest",
+                    LogBufferHelperKt.logcatLogBuffer("TouchMonitorTest"));
             clearInvocations(mLifecycleRegistry);
             mMonitor.init();
 
@@ -711,6 +713,16 @@
     }
 
     @Test
+    public void testDestroy_cleansUpHandler() {
+        final TouchHandler touchHandler = createTouchHandler();
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+        environment.destroyMonitor();
+        verify(touchHandler).onDestroy();
+    }
+
+    @Test
     public void testLastSessionPop_createsNewInputSession() {
         final TouchHandler touchHandler = createTouchHandler();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index f94a6f2..1e23690 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -41,6 +41,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.widget.LockPatternUtils
 import com.android.launcher3.icons.IconProvider
@@ -111,6 +112,7 @@
     @Mock lateinit var selectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var activityTaskManager: ActivityTaskManager
+    @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
 
     private lateinit var displayRepository: FakeDisplayRepository
     private lateinit var displayStateInteractor: DisplayStateInteractor
@@ -665,7 +667,8 @@
             ),
             { credentialViewModel },
             fakeExecutor,
-            vibrator
+            vibrator,
+            lazyViewCapture
         ) {
         override fun postOnAnimation(runnable: Runnable) {
             runnable.run()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 6047e7d..4fc4166 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -717,13 +717,9 @@
             assertThat(confirmHaptics?.hapticFeedbackConstant)
                 .isEqualTo(
                     if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS
-                    else HapticFeedbackConstants.CONFIRM
+                    else HapticFeedbackConstants.BIOMETRIC_CONFIRM
                 )
-            assertThat(confirmHaptics?.flag)
-                .isEqualTo(
-                    if (expectConfirmation) null
-                    else HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING
-                )
+            assertThat(confirmHaptics?.flag).isNull()
 
             if (expectConfirmation) {
                 kosmos.promptViewModel.confirmAuthenticated()
@@ -731,9 +727,8 @@
 
             val confirmedHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
             assertThat(confirmedHaptics?.hapticFeedbackConstant)
-                .isEqualTo(HapticFeedbackConstants.CONFIRM)
-            assertThat(confirmedHaptics?.flag)
-                .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+                .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+            assertThat(confirmedHaptics?.flag).isNull()
         }
 
     @Test
@@ -747,9 +742,8 @@
 
         val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
         assertThat(currentHaptics?.hapticFeedbackConstant)
-            .isEqualTo(HapticFeedbackConstants.CONFIRM)
-        assertThat(currentHaptics?.flag)
-            .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+            .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
+        assertThat(currentHaptics?.flag).isNull()
     }
 
     @Test
@@ -757,9 +751,9 @@
         kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false)
 
         val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
-        assertThat(currentHaptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT)
-        assertThat(currentHaptics?.flag)
-            .isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+        assertThat(currentHaptics?.hapticFeedbackConstant)
+            .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+        assertThat(currentHaptics?.flag).isNull()
     }
 
     // biometricprompt_sfps_fingerprint_authenticating reused across rotations
@@ -870,8 +864,9 @@
         )
 
         val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
-        assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT)
-        assertThat(haptics?.flag).isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+        assertThat(haptics?.hapticFeedbackConstant)
+            .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+        assertThat(haptics?.flag).isNull()
     }
 
     @Test
@@ -901,10 +896,12 @@
 
         val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay)
         if (expectConfirmation) {
-            assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.REJECT)
-            assertThat(haptics?.flag).isEqualTo(HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING)
+            assertThat(haptics?.hapticFeedbackConstant)
+                .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT)
+            assertThat(haptics?.flag).isNull()
         } else {
-            assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
+            assertThat(haptics?.hapticFeedbackConstant)
+                .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM)
         }
     }
 
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/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 88bfcf0..a1bea06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -39,8 +39,6 @@
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -84,7 +82,6 @@
     private AccessibilityManager mAccessibilityManager;
 
     private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
-    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     private final FalsingClassifier.Result mFalsedResult =
             FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
@@ -110,7 +107,7 @@
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
                 mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
                 mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
-                mAccessibilityManager, false, mFakeFeatureFlags);
+                mAccessibilityManager, false);
 
 
         ArgumentCaptor<GestureFinalizedListener> gestureCompleteListenerCaptor =
@@ -120,7 +117,6 @@
                 gestureCompleteListenerCaptor.capture());
 
         mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
-        mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index c425e82..5fc1971 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -26,7 +27,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -47,6 +47,8 @@
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.PersistableBundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.view.WindowInsets;
 import android.view.textclassifier.TextLinks;
 
@@ -130,7 +132,6 @@
                 new ClipData.Item("Test Item"));
 
         mFeatureFlags.set(CLIPBOARD_IMAGE_TIMEOUT, true); // turned off for legacy tests
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, true); // turned off for old tests
     }
 
     /**
@@ -299,8 +300,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
         initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
@@ -311,6 +312,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onShareTapped() {
         initController();
         mOverlayController.setClipData(mSampleClipData, "");
@@ -324,8 +326,8 @@
     }
 
     @Test
+    @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onDismissTapped_sharedTransitionsOff() {
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
         initController();
         mOverlayController.setClipData(mSampleClipData, "");
 
@@ -336,6 +338,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onDismissTapped() {
         initController();
 
@@ -350,7 +353,6 @@
 
     @Test
     public void test_multipleDismissals_dismissesOnce_sharedTransitionsOff() {
-        mFeatureFlags.set(CLIPBOARD_SHARED_TRANSITIONS, false);
         initController();
         mCallbacks.onSwipeDismissInitiated(mAnimator);
         mCallbacks.onDismissButtonTapped();
@@ -362,6 +364,7 @@
     }
 
     @Test
+    @EnableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_multipleDismissals_dismissesOnce() {
         initController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index e60848b..6e9b24f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -123,19 +123,19 @@
                 FakeWidgetMetadata(
                     widgetId = 11,
                     componentName = "com.android.fakePackage1/fakeWidget1",
-                    rank = 3,
+                    rank = 0,
                     userSerialNumber = 0,
                 ),
                 FakeWidgetMetadata(
                     widgetId = 12,
                     componentName = "com.android.fakePackage2/fakeWidget2",
-                    rank = 2,
+                    rank = 1,
                     userSerialNumber = 0,
                 ),
                 FakeWidgetMetadata(
                     widgetId = 13,
                     componentName = "com.android.fakePackage3/fakeWidget3",
-                    rank = 1,
+                    rank = 2,
                     userSerialNumber = 10,
                 ),
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
index eb0ab78..ad25502 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt
@@ -72,6 +72,82 @@
         databaseV2.verifyWidgetsV2(fakeWidgetsV1.map { it.getV2() })
     }
 
+    @Test
+    fun migrate2To3_noGapBetweenRanks_ranksReversed() {
+        // Create a communal database in version 2
+        val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)
+
+        // Populate some fake data
+        val fakeRanks =
+            listOf(
+                FakeCommunalItemRank(3),
+                FakeCommunalItemRank(2),
+                FakeCommunalItemRank(1),
+                FakeCommunalItemRank(0),
+            )
+        databaseV2.insertRanks(fakeRanks)
+
+        // Verify fake ranks populated
+        databaseV2.verifyRanksInOrder(fakeRanks)
+
+        // Run migration and get database V3
+        val databaseV3 =
+            migrationTestHelper.runMigrationsAndValidate(
+                name = DATABASE_NAME,
+                version = 3,
+                validateDroppedTables = false,
+                CommunalDatabase.MIGRATION_2_3,
+            )
+
+        // Verify ranks are reversed
+        databaseV3.verifyRanksInOrder(
+            listOf(
+                FakeCommunalItemRank(0),
+                FakeCommunalItemRank(1),
+                FakeCommunalItemRank(2),
+                FakeCommunalItemRank(3),
+            )
+        )
+    }
+
+    @Test
+    fun migrate2To3_withGapBetweenRanks_ranksReversed() {
+        // Create a communal database in version 2
+        val databaseV2 = migrationTestHelper.createDatabase(DATABASE_NAME, version = 2)
+
+        // Populate some fake data with gaps between ranks
+        val fakeRanks =
+            listOf(
+                FakeCommunalItemRank(9),
+                FakeCommunalItemRank(7),
+                FakeCommunalItemRank(2),
+                FakeCommunalItemRank(0),
+            )
+        databaseV2.insertRanks(fakeRanks)
+
+        // Verify fake ranks populated
+        databaseV2.verifyRanksInOrder(fakeRanks)
+
+        // Run migration and get database V3
+        val databaseV3 =
+            migrationTestHelper.runMigrationsAndValidate(
+                name = DATABASE_NAME,
+                version = 3,
+                validateDroppedTables = false,
+                CommunalDatabase.MIGRATION_2_3,
+            )
+
+        // Verify ranks are reversed
+        databaseV3.verifyRanksInOrder(
+            listOf(
+                FakeCommunalItemRank(0),
+                FakeCommunalItemRank(2),
+                FakeCommunalItemRank(7),
+                FakeCommunalItemRank(9),
+            )
+        )
+    }
+
     private fun SupportSQLiteDatabase.insertWidgetsV1(widgets: List<FakeCommunalWidgetItemV1>) {
         widgets.forEach { widget ->
             execSQL(
@@ -117,6 +193,25 @@
         assertThat(cursor.isAfterLast).isTrue()
     }
 
+    private fun SupportSQLiteDatabase.insertRanks(ranks: List<FakeCommunalItemRank>) {
+        ranks.forEach { rank ->
+            execSQL("INSERT INTO communal_item_rank_table(rank) VALUES(${rank.rank})")
+        }
+    }
+
+    private fun SupportSQLiteDatabase.verifyRanksInOrder(ranks: List<FakeCommunalItemRank>) {
+        val cursor = query("SELECT * FROM communal_item_rank_table ORDER BY uid")
+        assertThat(cursor.moveToFirst()).isTrue()
+
+        ranks.forEach { rank ->
+            assertThat(cursor.getInt(cursor.getColumnIndex("rank"))).isEqualTo(rank.rank)
+            cursor.moveToNext()
+        }
+
+        // Verify there is no more columns
+        assertThat(cursor.isAfterLast).isTrue()
+    }
+
     /**
      * Returns the expected data after migration from V1 to V2, which is simply that the new user
      * serial number field is now set to [CommunalWidgetItem.USER_SERIAL_NUMBER_UNDEFINED].
@@ -143,6 +238,10 @@
         val userSerialNumber: Int,
     )
 
+    private data class FakeCommunalItemRank(
+        val rank: Int,
+    )
+
     companion object {
         private const val DATABASE_NAME = "communal_db"
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 0b7a3ed..40b2a08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -22,6 +22,7 @@
 import android.os.PowerManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.keyguard.keyguardUpdateMonitor
 import com.android.keyguard.trustManager
 import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@
 import com.android.systemui.deviceentry.data.repository.fakeFaceWakeUpTriggersConfig
 import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -52,12 +54,16 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.eq
 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.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -113,6 +119,7 @@
                 powerInteractor,
                 fakeBiometricSettingsRepository,
                 trustManager,
+                { kosmos.sceneInteractor },
                 deviceEntryFaceAuthStatusInteractor,
             )
     }
@@ -279,6 +286,22 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun withSceneContainerEnabled_faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+        testScope.runTest {
+            underTest.start()
+
+            kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "for-test")
+            kosmos.sceneInteractor.setTransitionState(
+                MutableStateFlow(ObservableTransitionState.Idle(Scenes.Bouncer))
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, false))
+        }
+
+    @Test
     fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
         testScope.runTest {
             underTest.start()
@@ -339,15 +362,88 @@
         }
 
     @Test
-    fun faceAuthIsRequestedWhenQsExpansionStared() =
+    fun faceAuthIsRequestedWhenShadeExpansionStarted() =
         testScope.runTest {
             underTest.start()
 
-            underTest.onQsExpansionStared()
+            underTest.onShadeExpansionStarted()
 
             runCurrent()
             assertThat(faceAuthRepository.runningAuthRequest.value)
-                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true))
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, false))
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun faceAuthIsRequestedWhenShadeExpansionIsStarted() =
+        testScope.runTest {
+            underTest.start()
+            faceAuthRepository.canRunFaceAuth.value = true
+            kosmos.sceneInteractor.snapToScene(toScene = Scenes.Lockscreen, "for-test")
+            runCurrent()
+
+            kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "for-test")
+            kosmos.sceneInteractor.setTransitionState(
+                MutableStateFlow(
+                    ObservableTransitionState.Transition(
+                        fromScene = Scenes.Lockscreen,
+                        toScene = Scenes.Shade,
+                        currentScene = flowOf(Scenes.Lockscreen),
+                        progress = MutableStateFlow(0.2f),
+                        isInitiatedByUserInput = true,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, false))
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun faceAuthIsRequestedOnlyOnceWhenShadeExpansionStarts() =
+        testScope.runTest {
+            underTest.start()
+            faceAuthRepository.canRunFaceAuth.value = true
+            kosmos.sceneInteractor.snapToScene(toScene = Scenes.Lockscreen, "for-test")
+            runCurrent()
+
+            kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "for-test")
+            kosmos.sceneInteractor.setTransitionState(
+                MutableStateFlow(
+                    ObservableTransitionState.Transition(
+                        fromScene = Scenes.Lockscreen,
+                        toScene = Scenes.Shade,
+                        currentScene = flowOf(Scenes.Lockscreen),
+                        progress = MutableStateFlow(0.2f),
+                        isInitiatedByUserInput = true,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            )
+
+            runCurrent()
+            assertThat(faceAuthRepository.runningAuthRequest.value)
+                .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, false))
+            faceAuthRepository.runningAuthRequest.value = null
+
+            // expansion progress shouldn't trigger face auth again
+            kosmos.sceneInteractor.setTransitionState(
+                MutableStateFlow(
+                    ObservableTransitionState.Transition(
+                        fromScene = Scenes.Lockscreen,
+                        toScene = Scenes.Shade,
+                        currentScene = flowOf(Scenes.Lockscreen),
+                        progress = MutableStateFlow(0.5f),
+                        isInitiatedByUserInput = true,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            )
+
+            assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 0ac04b6..76539d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -467,6 +467,16 @@
             assertThat(values.toIdSets()).containsExactly(setOf(0, 1, 2))
         }
 
+    @Test
+    fun displayFlow_onlyDefaultDisplayAvailable_neverEmitsEmptySet() =
+        testScope.runTest {
+            setDisplays(0)
+
+            val values: List<Set<Display>> by collectValues(displayRepository.displays)
+
+            assertThat(values.toIdSets()).containsExactly(setOf(0))
+        }
+
     private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
 
     private fun Iterable<Set<Display>>.toIdSets(): List<Set<Int>> = map { it.ids().toSet() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 933ddb5..4a80d72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -21,7 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.haptics.fakeVibratorHelper
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.fakeSystemClock
@@ -47,6 +47,7 @@
     private val lowTickDuration = 12 // Mocked duration of a low tick
     private val dragTextureThresholdMillis =
         lowTickDuration * config.numberOfLowTicks + config.deltaMillisForDragInterval
+    private val vibratorHelper = kosmos.fakeVibratorHelper
     private lateinit var sliderHapticFeedbackProvider: SliderHapticFeedbackProvider
 
     @Before
@@ -56,11 +57,11 @@
         whenever(velocityTracker.getAxisVelocity(config.velocityAxis))
             .thenReturn(config.maxVelocityToScale)
 
-        kosmos.vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_LOW_TICK] =
+        vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_LOW_TICK] =
             lowTickDuration
         sliderHapticFeedbackProvider =
             SliderHapticFeedbackProvider(
-                kosmos.vibratorHelper,
+                vibratorHelper,
                 velocityTracker,
                 config,
                 kosmos.fakeSystemClock,
@@ -136,7 +137,7 @@
             sliderHapticFeedbackProvider.onUpperBookend()
             sliderHapticFeedbackProvider.onUpperBookend()
 
-            assertEquals(/* expected=*/ 1, vibratorHelper.timesVibratedWithEffect(vibration))
+            assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(vibration))
         }
 
     @Test
@@ -162,7 +163,7 @@
             sliderHapticFeedbackProvider.onProgress(progress)
 
             // THEN the correct composition only plays once
-            assertEquals(/* expected=*/ 1, vibratorHelper.timesVibratedWithEffect(ticks.compose()))
+            assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(ticks.compose()))
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.kt
new file mode 100644
index 0000000..945f953
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialNotificationCoordinatorTest.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.inputdevice.tutorial.domain.interactor
+
+import android.app.Notification
+import android.app.NotificationManager
+import androidx.annotation.StringRes
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.touchpad.data.repository.FakeTouchpadRepository
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.hours
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+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.Captor
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TutorialNotificationCoordinatorTest : SysuiTestCase() {
+
+    private lateinit var underTest: TutorialNotificationCoordinator
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val keyboardRepository = FakeKeyboardRepository()
+    private val touchpadRepository = FakeTouchpadRepository()
+    private lateinit var dataStoreScope: CoroutineScope
+    private lateinit var repository: TutorialSchedulerRepository
+    @Mock private lateinit var notificationManager: NotificationManager
+    @Captor private lateinit var notificationCaptor: ArgumentCaptor<Notification>
+    @get:Rule val rule = MockitoJUnit.rule()
+
+    @Before
+    fun setup() {
+        dataStoreScope = CoroutineScope(Dispatchers.Unconfined)
+        repository =
+            TutorialSchedulerRepository(
+                context,
+                dataStoreScope,
+                dataStoreName = "TutorialNotificationCoordinatorTest"
+            )
+        val interactor =
+            TutorialSchedulerInteractor(keyboardRepository, touchpadRepository, repository)
+        underTest =
+            TutorialNotificationCoordinator(
+                testScope.backgroundScope,
+                context,
+                interactor,
+                notificationManager
+            )
+        notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
+        underTest.start()
+    }
+
+    @After
+    fun clear() {
+        runBlocking { repository.clearDataStore() }
+        dataStoreScope.cancel()
+    }
+
+    @Test
+    fun showKeyboardNotification() =
+        testScope.runTest {
+            keyboardRepository.setIsAnyKeyboardConnected(true)
+            advanceTimeBy(LAUNCH_DELAY)
+            verifyNotification(
+                R.string.launch_keyboard_tutorial_notification_title,
+                R.string.launch_keyboard_tutorial_notification_content
+            )
+        }
+
+    @Test
+    fun showTouchpadNotification() =
+        testScope.runTest {
+            touchpadRepository.setIsAnyTouchpadConnected(true)
+            advanceTimeBy(LAUNCH_DELAY)
+            verifyNotification(
+                R.string.launch_touchpad_tutorial_notification_title,
+                R.string.launch_touchpad_tutorial_notification_content
+            )
+        }
+
+    @Test
+    fun showKeyboardTouchpadNotification() =
+        testScope.runTest {
+            keyboardRepository.setIsAnyKeyboardConnected(true)
+            touchpadRepository.setIsAnyTouchpadConnected(true)
+            advanceTimeBy(LAUNCH_DELAY)
+            verifyNotification(
+                R.string.launch_keyboard_touchpad_tutorial_notification_title,
+                R.string.launch_keyboard_touchpad_tutorial_notification_content
+            )
+        }
+
+    @Test
+    fun doNotShowNotification() =
+        testScope.runTest {
+            advanceTimeBy(LAUNCH_DELAY)
+            verify(notificationManager, never()).notify(eq(TAG), eq(NOTIFICATION_ID), any())
+        }
+
+    private fun verifyNotification(@StringRes titleResId: Int, @StringRes contentResId: Int) {
+        verify(notificationManager)
+            .notify(eq(TAG), eq(NOTIFICATION_ID), notificationCaptor.capture())
+        val notification = notificationCaptor.value
+        val actualTitle = notification.getString(Notification.EXTRA_TITLE)
+        val actualContent = notification.getString(Notification.EXTRA_TEXT)
+        assertThat(actualTitle).isEqualTo(context.getString(titleResId))
+        assertThat(actualContent).isEqualTo(context.getString(contentResId))
+    }
+
+    private fun Notification.getString(key: String): String =
+        this.extras?.getCharSequence(key).toString()
+
+    companion object {
+        private const val TAG = "TutorialSchedulerInteractor"
+        private const val NOTIFICATION_ID = 5566
+        private val LAUNCH_DELAY = 72.hours
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
index 432f7af..650f9dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/domain/interactor/TutorialSchedulerInteractorTest.kt
@@ -32,6 +32,8 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
@@ -63,13 +65,7 @@
                 dataStoreName = "TutorialSchedulerInteractorTest"
             )
         underTest =
-            TutorialSchedulerInteractor(
-                testScope.backgroundScope,
-                keyboardRepository,
-                touchpadRepository,
-                schedulerRepository
-            )
-        underTest.start()
+            TutorialSchedulerInteractor(keyboardRepository, touchpadRepository, schedulerRepository)
     }
 
     @After
@@ -81,80 +77,90 @@
     @Test
     fun connectKeyboard_delayElapse_launchForKeyboard() =
         testScope.runTest {
+            launchAndAssert(TutorialType.KEYBOARD)
+
             keyboardRepository.setIsAnyKeyboardConnected(true)
             advanceTimeBy(LAUNCH_DELAY)
-            assertLaunch(TutorialType.KEYBOARD)
         }
 
     @Test
     fun connectBothDevices_delayElapse_launchForBoth() =
         testScope.runTest {
+            launchAndAssert(TutorialType.BOTH)
+
             keyboardRepository.setIsAnyKeyboardConnected(true)
             touchpadRepository.setIsAnyTouchpadConnected(true)
             advanceTimeBy(LAUNCH_DELAY)
-            assertLaunch(TutorialType.BOTH)
         }
 
     @Test
     fun connectBothDevice_delayNotElapse_launchNothing() =
         testScope.runTest {
+            launchAndAssert(TutorialType.NONE)
+
             keyboardRepository.setIsAnyKeyboardConnected(true)
             touchpadRepository.setIsAnyTouchpadConnected(true)
             advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
-            assertLaunch(TutorialType.NONE)
         }
 
     @Test
     fun nothingConnect_delayElapse_launchNothing() =
         testScope.runTest {
+            launchAndAssert(TutorialType.NONE)
+
             keyboardRepository.setIsAnyKeyboardConnected(false)
             touchpadRepository.setIsAnyTouchpadConnected(false)
             advanceTimeBy(LAUNCH_DELAY)
-            assertLaunch(TutorialType.NONE)
         }
 
     @Test
     fun connectKeyboard_thenTouchpad_delayElapse_launchForBoth() =
         testScope.runTest {
+            launchAndAssert(TutorialType.BOTH)
+
             keyboardRepository.setIsAnyKeyboardConnected(true)
             advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
             touchpadRepository.setIsAnyTouchpadConnected(true)
             advanceTimeBy(REMAINING_TIME)
-            assertLaunch(TutorialType.BOTH)
         }
 
     @Test
     fun connectKeyboard_thenTouchpad_removeKeyboard_delayElapse_launchNothing() =
         testScope.runTest {
+            launchAndAssert(TutorialType.NONE)
+
             keyboardRepository.setIsAnyKeyboardConnected(true)
             advanceTimeBy(A_SHORT_PERIOD_OF_TIME)
             touchpadRepository.setIsAnyTouchpadConnected(true)
             keyboardRepository.setIsAnyKeyboardConnected(false)
             advanceTimeBy(REMAINING_TIME)
-            assertLaunch(TutorialType.NONE)
         }
 
-    // TODO: likely to be changed after we update TutorialSchedulerInteractor.launchTutorial
-    private suspend fun assertLaunch(tutorialType: TutorialType) {
-        when (tutorialType) {
-            TutorialType.KEYBOARD -> {
-                assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
-                assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
-            }
-            TutorialType.TOUCHPAD -> {
-                assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
-                assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
-            }
-            TutorialType.BOTH -> {
-                assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
-                assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
-            }
-            TutorialType.NONE -> {
-                assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
-                assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+    private suspend fun launchAndAssert(expectedTutorial: TutorialType) =
+        testScope.backgroundScope.launch {
+            val actualTutorial = underTest.tutorials.first()
+            assertThat(actualTutorial).isEqualTo(expectedTutorial)
+
+            // TODO: need to update after we move launch into the tutorial
+            when (expectedTutorial) {
+                TutorialType.KEYBOARD -> {
+                    assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
+                    assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+                }
+                TutorialType.TOUCHPAD -> {
+                    assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
+                    assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
+                }
+                TutorialType.BOTH -> {
+                    assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isTrue()
+                    assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isTrue()
+                }
+                TutorialType.NONE -> {
+                    assertThat(schedulerRepository.isLaunched(DeviceType.KEYBOARD)).isFalse()
+                    assertThat(schedulerRepository.isLaunched(DeviceType.TOUCHPAD)).isFalse()
+                }
             }
         }
-    }
 
     companion object {
         private val LAUNCH_DELAY = 72.hours
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 9d9e5be62..3ccb989 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -34,14 +34,16 @@
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -57,29 +59,28 @@
 @RunWith(AndroidJUnit4::class)
 class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
 
-    private val dispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(dispatcher)
+    private val kosmos = testKosmos()
+    private val dispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
     private lateinit var viewModel: StickyKeysIndicatorViewModel
     private val inputManager = mock<InputManager>()
     private val keyboardRepository = FakeKeyboardRepository()
-    private val secureSettings = FakeSettings()
+    private val secureSettings = kosmos.fakeSettings
     private val userRepository = Kosmos().fakeUserRepository
     private val captor =
         ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)
 
     @Before
     fun setup() {
-        val settingsRepository = UserAwareSecureSettingsRepositoryImpl(
-            secureSettings,
-            userRepository,
-            dispatcher
-        )
-        val stickyKeysRepository = StickyKeysRepositoryImpl(
-            inputManager,
-            dispatcher,
-            settingsRepository,
-            mock<StickyKeysLogger>()
-        )
+        val settingsRepository =
+            UserAwareSecureSettingsRepositoryImpl(secureSettings, userRepository, dispatcher)
+        val stickyKeysRepository =
+            StickyKeysRepositoryImpl(
+                inputManager,
+                dispatcher,
+                settingsRepository,
+                mock<StickyKeysLogger>()
+            )
         setStickyKeySetting(enabled = false)
         viewModel =
             StickyKeysIndicatorViewModel(
@@ -182,16 +183,16 @@
             val stickyKeys by collectLastValue(viewModel.indicatorContent)
             setStickyKeysActive()
 
-            setStickyKeys(mapOf(
-                ALT to false,
-                META to false,
-                SHIFT to false))
+            setStickyKeys(mapOf(ALT to false, META to false, SHIFT to false))
 
-            assertThat(stickyKeys).isEqualTo(mapOf(
-                ALT to Locked(false),
-                META to Locked(false),
-                SHIFT to Locked(false),
-            ))
+            assertThat(stickyKeys)
+                .isEqualTo(
+                    mapOf(
+                        ALT to Locked(false),
+                        META to Locked(false),
+                        SHIFT to Locked(false),
+                    )
+                )
         }
     }
 
@@ -201,9 +202,7 @@
             val stickyKeys by collectLastValue(viewModel.indicatorContent)
             setStickyKeysActive()
 
-            setStickyKeys(mapOf(
-                ALT to false,
-                ALT to true))
+            setStickyKeys(mapOf(ALT to false, ALT to true))
 
             assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(true)))
         }
@@ -215,17 +214,23 @@
             val stickyKeys by collectLastValue(viewModel.indicatorContent)
             setStickyKeysActive()
 
-            setStickyKeys(mapOf(
-                META to false,
-                SHIFT to false, // shift is sticky but not locked
-                CTRL to false))
+            setStickyKeys(
+                mapOf(
+                    META to false,
+                    SHIFT to false, // shift is sticky but not locked
+                    CTRL to false
+                )
+            )
             val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false))
 
-            setStickyKeys(mapOf(
-                SHIFT to false,
-                SHIFT to true, // shift is now locked
-                META to false,
-                CTRL to false))
+            setStickyKeys(
+                mapOf(
+                    SHIFT to false,
+                    SHIFT to true, // shift is now locked
+                    META to false,
+                    CTRL to false
+                )
+            )
             assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true)))
                 .isEqualTo(previousShiftIndex)
         }
@@ -247,17 +252,27 @@
         StickyModifierState() {
 
         private fun isOn(key: ModifierKey) = keys.any { it.key == key && !it.value }
+
         private fun isLocked(key: ModifierKey) = keys.any { it.key == key && it.value }
 
         override fun isAltGrModifierLocked() = isLocked(ALT_GR)
+
         override fun isAltGrModifierOn() = isOn(ALT_GR)
+
         override fun isAltModifierLocked() = isLocked(ALT)
+
         override fun isAltModifierOn() = isOn(ALT)
+
         override fun isCtrlModifierLocked() = isLocked(CTRL)
+
         override fun isCtrlModifierOn() = isOn(CTRL)
+
         override fun isMetaModifierLocked() = isLocked(META)
+
         override fun isMetaModifierOn() = isOn(META)
+
         override fun isShiftModifierLocked() = isLocked(SHIFT)
+
         override fun isShiftModifierOn() = isOn(SHIFT)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 29cd9a2..fa69fdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -50,6 +50,8 @@
 import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
 import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRendererFactory
 import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.kosmos.unconfinedTestScope
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -64,12 +66,12 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.settings.unconfinedDispatcherFakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-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
@@ -87,6 +89,11 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class CustomizationProviderTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.unconfinedTestDispatcher
+    private val testScope = kosmos.unconfinedTestScope
+    private val fakeSettings = kosmos.unconfinedDispatcherFakeSettings
+
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var userTracker: UserTracker
@@ -104,9 +111,6 @@
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
 
     private lateinit var underTest: CustomizationProvider
-    private lateinit var testScope: TestScope
-
-    private val kosmos = testKosmos()
 
     @Before
     fun setUp() {
@@ -120,8 +124,6 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
 
         underTest = CustomizationProvider()
-        val testDispatcher = UnconfinedTestDispatcher()
-        testScope = TestScope(testDispatcher)
         val localUserSelectionManager =
             KeyguardQuickAffordanceLocalUserSelectionManager(
                 context = context,
@@ -170,7 +172,7 @@
                     KeyguardQuickAffordanceLegacySettingSyncer(
                         scope = testScope.backgroundScope,
                         backgroundDispatcher = testDispatcher,
-                        secureSettings = FakeSettings(),
+                        secureSettings = fakeSettings,
                         selectionsManager = localUserSelectionManager,
                     ),
                 dumpManager = mock(),
@@ -216,7 +218,7 @@
                 mainDispatcher = testDispatcher,
                 backgroundHandler = backgroundHandler,
             )
-        underTest.mainDispatcher = UnconfinedTestDispatcher()
+        underTest.mainDispatcher = testDispatcher
 
         underTest.attachInfoForTesting(
             context,
@@ -319,6 +321,7 @@
                         ),
                     )
                 )
+            runCurrent()
         }
 
     @Test
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/data/repository/KeyguardClockRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
index af5187d..1e9db64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
@@ -25,15 +25,14 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
 import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth
 import kotlin.test.Test
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.runner.RunWith
@@ -44,12 +43,12 @@
 @SmallTest
 class KeyguardClockRepositoryTest : SysuiTestCase() {
 
-    private lateinit var scheduler: TestCoroutineScheduler
-    private lateinit var dispatcher: CoroutineDispatcher
-    private lateinit var scope: TestScope
+    private val kosmos = testKosmos()
+    private val dispatcher = kosmos.testDispatcher
+    private val scope = kosmos.testScope
+    private val fakeSettings = kosmos.fakeSettings
 
     private lateinit var underTest: KeyguardClockRepository
-    private lateinit var fakeSettings: FakeSettings
     @Mock private lateinit var clockRegistry: ClockRegistry
     @Mock private lateinit var clockEventController: ClockEventController
     private val fakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
@@ -57,10 +56,6 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        fakeSettings = FakeSettings()
-        scheduler = TestCoroutineScheduler()
-        dispatcher = StandardTestDispatcher(scheduler)
-        scope = TestScope(dispatcher)
         underTest =
             KeyguardClockRepositoryImpl(
                 fakeSettings,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
index 8b8a6cb..5a597fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
@@ -21,14 +21,12 @@
 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.settings.FakeUserTracker
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth
 import kotlin.test.Test
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.runner.RunWith
@@ -38,23 +36,18 @@
 @SmallTest
 class KeyguardSmartspaceRepositoryImplTest : SysuiTestCase() {
 
-    private lateinit var scheduler: TestCoroutineScheduler
-    private lateinit var dispatcher: CoroutineDispatcher
-    private lateinit var scope: TestScope
+    private val kosmos = testKosmos()
+    private val scope = kosmos.testScope
+    private val fakeSettings = kosmos.fakeSettings
 
     private lateinit var underTest: KeyguardSmartspaceRepository
-    private lateinit var fakeSettings: FakeSettings
     private lateinit var fakeUserTracker: FakeUserTracker
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        fakeSettings = FakeSettings()
         fakeUserTracker = FakeUserTracker()
         fakeSettings.userId = fakeUserTracker.userId
-        scheduler = TestCoroutineScheduler()
-        dispatcher = StandardTestDispatcher(scheduler)
-        scope = TestScope(dispatcher)
         underTest =
             KeyguardSmartspaceRepositoryImpl(
                 context = context,
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/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index fc7f693..1929cd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.dock.DockManagerFake
 import com.android.systemui.doze.util.BurnInHelperWrapper
 import com.android.systemui.flags.FakeFeatureFlags
@@ -53,6 +54,8 @@
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -68,13 +71,11 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.max
 import kotlin.math.min
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -92,6 +93,11 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
+
     @Mock private lateinit var expandable: Expandable
     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -107,7 +113,6 @@
 
     private lateinit var underTest: KeyguardBottomAreaViewModel
 
-    private lateinit var testScope: TestScope
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
@@ -115,8 +120,6 @@
     private lateinit var dockManager: DockManagerFake
     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
 
-    private val kosmos = testKosmos()
-
     init {
         mSetFlagsRule.setFlagsParameterization(flags)
     }
@@ -152,9 +155,7 @@
         dockManager = DockManagerFake()
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         val featureFlags =
-            FakeFeatureFlags().apply {
-                set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
-            }
+            FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) }
 
         val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
         val keyguardInteractor = withDeps.keyguardInteractor
@@ -163,8 +164,6 @@
         whenever(userTracker.userHandle).thenReturn(mock())
         whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
             .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
-        val testDispatcher = StandardTestDispatcher()
-        testScope = TestScope(testDispatcher)
         val localUserSelectionManager =
             KeyguardQuickAffordanceLocalUserSelectionManager(
                 context = context,
@@ -200,7 +199,7 @@
                     KeyguardQuickAffordanceLegacySettingSyncer(
                         scope = testScope.backgroundScope,
                         backgroundDispatcher = testDispatcher,
-                        secureSettings = FakeSettings(),
+                        secureSettings = settings,
                         selectionsManager = localUserSelectionManager,
                     ),
                 configs =
@@ -223,6 +222,7 @@
                 broadcastDispatcher = broadcastDispatcher,
                 accessibilityManager = accessibilityManager,
                 pulsingGestureListener = kosmos.pulsingGestureListener,
+                faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
             )
         underTest =
             KeyguardBottomAreaViewModel(
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..720f2e1 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
@@ -66,7 +72,7 @@
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth
 import kotlin.math.min
 import kotlin.test.assertEquals
@@ -88,6 +94,10 @@
 @RunWith(AndroidJUnit4::class)
 class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
+
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var expandable: Expandable
@@ -144,13 +154,9 @@
     @Mock
     private lateinit var glanceableHubToLockscreenTransitionViewModel:
         GlanceableHubToLockscreenTransitionViewModel
-    @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
-
-    private val kosmos = testKosmos()
 
     private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
 
-    private val testScope = kosmos.testScope
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
@@ -163,8 +169,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)
@@ -241,7 +245,7 @@
                     KeyguardQuickAffordanceLegacySettingSyncer(
                         scope = testScope.backgroundScope,
                         backgroundDispatcher = kosmos.testDispatcher,
-                        secureSettings = FakeSettings(),
+                        secureSettings = settings,
                         selectionsManager = localUserSelectionManager,
                     ),
                 configs =
@@ -256,7 +260,6 @@
 
         intendedAlphaMutableStateFlow.value = 1f
         intendedShadeAlphaMutableStateFlow.value = 0f
-        intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
         whenever(aodToLockscreenTransitionViewModel.shortcutsAlpha)
             .thenReturn(intendedAlphaMutableStateFlow)
         whenever(dozingToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
@@ -283,8 +286,6 @@
         whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
             .thenReturn(emptyFlow())
         whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
-        whenever(transitionInteractor.finishedKeyguardState)
-            .thenReturn(intendedFinishedKeyguardStateFlow)
 
         underTest =
             KeyguardQuickAffordancesCombinedViewModel(
@@ -334,7 +335,7 @@
                     lockscreenToPrimaryBouncerTransitionViewModel,
                 lockscreenToGlanceableHubTransitionViewModel =
                     lockscreenToGlanceableHubTransitionViewModel,
-                transitionInteractor = transitionInteractor,
+                transitionInteractor = kosmos.keyguardTransitionInteractor,
             )
     }
 
@@ -403,7 +404,7 @@
         }
 
     @Test
-    @EnableFlags(com.android.systemui.Flags.FLAG_NEW_PICKER_UI)
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     fun startButton_inPreviewMode_onPreviewQuickAffordanceSelected() =
         testScope.runTest {
             underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
@@ -776,7 +777,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 +792,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/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
index 67517a2..2ba670c 100644
--- a/packages/SystemUI/multivalentTests/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/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
similarity index 63%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index 7d57220..73f724e 100644
--- a/packages/SystemUI/multivalentTests/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/log/table/LogDiffsForTableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
index e55cb12..030b172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
@@ -19,8 +19,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.LogcatEchoTrackerAlways
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
@@ -57,9 +57,7 @@
                 MAX_SIZE,
                 BUFFER_NAME,
                 systemClock,
-                mock(),
-                testDispatcher,
-                testScope.backgroundScope,
+                LogcatEchoTrackerAlways(),
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
index 8c62bc2..dfd964f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
@@ -20,25 +20,20 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.log.LogcatEchoTrackerAlways
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class TableLogBufferFactoryTest : SysuiTestCase() {
     private val dumpManager: DumpManager = mock()
     private val systemClock = FakeSystemClock()
-    private val testDispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
     private val underTest =
-        TableLogBufferFactory(dumpManager, systemClock, mock(), testDispatcher, testScope)
+        TableLogBufferFactory(dumpManager, systemClock, LogcatEchoTrackerAlways())
 
     @Test
     fun create_alwaysCreatesNewInstance() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index ace562b..9c4c862 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -23,22 +23,18 @@
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
 import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 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.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class TableLogBufferTest : SysuiTestCase() {
@@ -49,9 +45,6 @@
     private lateinit var logcatEchoTracker: LogcatEchoTracker
     private lateinit var localLogcat: FakeLogProxy
 
-    private val testDispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
-
     @Before
     fun setup() {
         localLogcat = FakeLogProxy()
@@ -65,8 +58,6 @@
                 NAME,
                 systemClock,
                 logcatEchoTracker,
-                testDispatcher,
-                testScope.backgroundScope,
                 localLogcat = localLogcat,
             )
     }
@@ -78,8 +69,6 @@
             "name",
             systemClock,
             logcatEchoTracker,
-            testDispatcher,
-            testScope.backgroundScope,
             localLogcat = localLogcat,
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index bdee936..ad7a5b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -38,19 +38,27 @@
 import android.media.session.PlaybackState
 import android.net.Uri
 import android.os.Bundle
+import android.platform.test.flag.junit.FlagsParameterization
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
 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.dump.DumpManager
+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.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.domain.resume.MediaResumeListener
 import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
 import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
@@ -58,16 +66,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.testKosmos
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 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.After
 import org.junit.Before
 import org.junit.Rule
@@ -91,6 +104,8 @@
 import org.mockito.kotlin.capture
 import org.mockito.kotlin.eq
 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"
@@ -111,13 +126,13 @@
     return Mockito.anyObject<T>()
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-class LegacyMediaDataManagerImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class LegacyMediaDataManagerImplTest(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
@@ -136,7 +151,6 @@
     @Mock lateinit var mediaDataFilter: LegacyMediaDataFilterImpl
     @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
     lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@@ -144,7 +158,6 @@
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
     lateinit var validRecommendationList: List<SmartspaceAction>
     @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
-    @Mock private lateinit var mediaFlags: MediaFlags
     @Mock private lateinit var logger: MediaUiEventLogger
     lateinit var mediaDataManager: LegacyMediaDataManagerImpl
     lateinit var mediaNotification: StatusBarNotification
@@ -159,6 +172,26 @@
     @Mock private lateinit var ugm: IUriGrantsManager
     @Mock private lateinit var imageSource: ImageDecoder.Source
 
+    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 instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
     private val originalSmartspaceSetting =
@@ -188,12 +221,16 @@
             Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
             1
         )
+
         mediaDataManager =
             LegacyMediaDataManagerImpl(
                 context = context,
                 backgroundExecutor = backgroundExecutor,
+                backgroundDispatcher = testDispatcher,
                 uiExecutor = uiExecutor,
                 foregroundExecutor = foregroundExecutor,
+                mainDispatcher = testDispatcher,
+                applicationScope = testScope,
                 mediaControllerFactory = mediaControllerFactory,
                 broadcastDispatcher = broadcastDispatcher,
                 dumpManager = dumpManager,
@@ -209,10 +246,11 @@
                 useQsMediaPlayer = true,
                 systemClock = clock,
                 tunerService = tunerService,
-                mediaFlags = mediaFlags,
+                mediaFlags = kosmos.mediaFlags,
                 logger = logger,
                 smartspaceManager = smartspaceManager,
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
+                mediaDataLoader = { kosmos.mediaDataLoader },
             )
         verify(tunerService)
             .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -248,7 +286,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())
@@ -278,10 +316,10 @@
         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_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)
     }
@@ -310,49 +348,51 @@
     }
 
     @Test
-    fun testsetInactive_resume_dismissesMedia() {
-        // WHEN resume controls are present, and time out
-        val desc =
-            MediaDescription.Builder().run {
-                setTitle(SESSION_TITLE)
-                build()
-            }
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-
-        backgroundExecutor.runAllReady()
-        foregroundExecutor.runAllReady()
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
+    fun testsetInactive_resume_dismissesMedia() =
+        testScope.runTest {
+            // WHEN resume controls are present, and time out
+            val desc =
+                MediaDescription.Builder().run {
+                    setTitle(SESSION_TITLE)
+                    build()
+                }
+            mediaDataManager.addResumptionControls(
+                USER_ID,
+                desc,
+                Runnable {},
+                session.sessionToken,
+                APP_NAME,
+                pendingIntent,
+                PACKAGE_NAME
             )
 
-        mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
-        verify(logger)
-            .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+            runCurrent()
+            backgroundExecutor.runAllReady()
+            foregroundExecutor.runAllReady()
+            verify(listener)
+                .onMediaDataLoaded(
+                    eq(PACKAGE_NAME),
+                    eq(null),
+                    capture(mediaDataCaptor),
+                    eq(true),
+                    eq(0),
+                    eq(false)
+                )
 
-        // THEN it is removed and listeners are informed
-        foregroundExecutor.advanceClockToLast()
-        foregroundExecutor.runAllReady()
-        verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
-    }
+            mediaDataManager.setInactive(PACKAGE_NAME, timedOut = true)
+            verify(logger)
+                .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId))
+
+            // THEN it is removed and listeners are informed
+            foregroundExecutor.advanceClockToLast()
+            foregroundExecutor.runAllReady()
+            verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
+        }
 
     @Test
     fun testLoadsMetadataOnBackground() {
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.numPending()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 0, background = 1)
     }
 
     @Test
@@ -370,8 +410,7 @@
         mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
 
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -389,8 +428,7 @@
         mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
 
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -417,11 +455,9 @@
 
     @Test
     fun testOnMetaDataLoaded_conservesActiveFlag() {
-        whenever(mediaControllerFactory.create(anyObject())).thenReturn(controller)
         mediaDataManager.addListener(listener)
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -465,8 +501,7 @@
             }
 
         mediaDataManager.onNotificationAdded(KEY, notif)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -552,8 +587,7 @@
         mediaDataManager.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),
@@ -583,8 +617,7 @@
         mediaDataManager.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),
@@ -625,8 +658,7 @@
         mediaDataManager.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),
@@ -734,8 +766,7 @@
         // GIVEN that the manager has two notifications with resume actions
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
+        testScope.assertRunAllReady(foreground = 2, background = 2)
 
         verify(listener)
             .onMediaDataLoaded(
@@ -822,7 +853,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())
@@ -853,7 +884,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)
@@ -972,7 +1003,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
@@ -999,7 +1030,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 =
@@ -1024,7 +1055,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 =
@@ -1050,7 +1081,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 =
@@ -1068,7 +1099,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 =
@@ -1087,8 +1118,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),
@@ -1102,7 +1132,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 =
@@ -1121,8 +1151,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),
@@ -1189,8 +1218,7 @@
         mediaDataManager.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),
@@ -1307,7 +1335,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
 
@@ -1333,7 +1361,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)
@@ -1367,7 +1395,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
@@ -1399,7 +1427,7 @@
 
     @Test
     fun testSetRecommendationInactive_notifiesListeners() {
-        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
 
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         val instanceId = instanceIdSequence.lastInstanceId
@@ -1479,8 +1507,7 @@
     fun testOnMediaDataTimedOut_updatesLastActiveTime() {
         // GIVEN that the manager has a notification
         mediaDataManager.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)
@@ -1588,8 +1615,7 @@
 
         // WHEN the notification is loaded
         mediaDataManager.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)
@@ -1624,8 +1650,7 @@
 
         // WHEN the notification is loaded
         mediaDataManager.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)
@@ -1644,7 +1669,6 @@
     @Test
     fun testPlaybackActions_noState_usesNotification() {
         val desc = "Notification Action"
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         whenever(controller.playbackState).thenReturn(null)
 
         val notifWithAction =
@@ -1659,8 +1683,7 @@
             }
         mediaDataManager.onNotificationAdded(KEY, notifWithAction)
 
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -1679,7 +1702,6 @@
     @Test
     fun testPlaybackActions_hasPrevNext() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions =
             PlaybackState.ACTION_PLAY or
                 PlaybackState.ACTION_SKIP_TO_PREVIOUS or
@@ -1723,7 +1745,6 @@
     @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)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
@@ -1755,7 +1776,6 @@
 
     @Test
     fun testPlaybackActions_connecting() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder =
             PlaybackState.Builder()
@@ -1776,7 +1796,6 @@
     @Test
     fun testPlaybackActions_reservedSpace() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
@@ -1814,7 +1833,6 @@
 
     @Test
     fun testPlaybackActions_playPause_hasButton() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions = PlaybackState.ACTION_PLAY_PAUSE
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -1851,8 +1869,7 @@
 
         // update to remote cast
         mediaDataManager.onNotificationAdded(KEY, remoteCastNotification)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(logger)
             .logPlaybackLocationChange(
                 anyInt(),
@@ -1914,7 +1931,6 @@
 
     @Test
     fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
         whenever(controller.playbackState).thenReturn(state)
 
@@ -1935,46 +1951,48 @@
     }
 
     @Test
-    fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() {
-        val desc =
-            MediaDescription.Builder().run {
-                setTitle(SESSION_TITLE)
-                build()
-            }
-        val state =
-            PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
-                .setActions(PlaybackState.ACTION_PLAY_PAUSE)
-                .build()
+    fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() =
+        testScope.runTest {
+            val desc =
+                MediaDescription.Builder().run {
+                    setTitle(SESSION_TITLE)
+                    build()
+                }
+            val state =
+                PlaybackState.Builder()
+                    .setState(PlaybackState.STATE_PAUSED, 0L, 1f)
+                    .setActions(PlaybackState.ACTION_PLAY_PAUSE)
+                    .build()
 
-        // Add resumption controls in order to have semantic actions.
-        // To make sure that they are not null after changing state.
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-        backgroundExecutor.runAllReady()
-        foregroundExecutor.runAllReady()
-
-        stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
-
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(PACKAGE_NAME),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
+            // Add resumption controls in order to have semantic actions.
+            // To make sure that they are not null after changing state.
+            mediaDataManager.addResumptionControls(
+                USER_ID,
+                desc,
+                Runnable {},
+                session.sessionToken,
+                APP_NAME,
+                pendingIntent,
+                PACKAGE_NAME
             )
-        assertThat(mediaDataCaptor.value.isPlaying).isFalse()
-        assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
-    }
+            runCurrent()
+            backgroundExecutor.runAllReady()
+            foregroundExecutor.runAllReady()
+
+            stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+
+            verify(listener)
+                .onMediaDataLoaded(
+                    eq(PACKAGE_NAME),
+                    eq(PACKAGE_NAME),
+                    capture(mediaDataCaptor),
+                    eq(true),
+                    eq(0),
+                    eq(false)
+                )
+            assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+            assertThat(mediaDataCaptor.value.semanticActions).isNotNull()
+        }
 
     @Test
     fun testPlaybackStateNull_Pause_keyExists_callsListener() {
@@ -2036,7 +2054,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()
@@ -2066,7 +2084,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()
@@ -2084,7 +2102,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()
@@ -2101,7 +2119,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()
@@ -2133,8 +2151,7 @@
 
     @Test
     fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2153,8 +2170,7 @@
 
     @Test
     fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2187,8 +2203,7 @@
 
     @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)
         addPlaybackStateAction()
 
         // When a media control using session actions is added, and then the session is destroyed
@@ -2207,8 +2222,7 @@
 
     @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)
         addPlaybackStateAction()
 
         // When a media control using session actions and that does allow resumption is added,
@@ -2241,7 +2255,6 @@
 
     @Test
     fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2268,7 +2281,6 @@
 
     @Test
     fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         addPlaybackStateAction()
 
         // When a media control using session actions is added, and then the session is destroyed
@@ -2287,7 +2299,6 @@
 
     @Test
     fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         addPlaybackStateAction()
 
         // When a media control using session actions and that does allow resumption is added,
@@ -2320,8 +2331,7 @@
 
     @Test
     fun testSessionDestroyed_noNotificationKey_stillRemoved() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a notiifcation is added and then removed before it is fully processed
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
@@ -2392,6 +2402,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)
@@ -2400,8 +2427,7 @@
     /** Helper function to add the given notification and capture the resulting MediaData */
     private fun addNotificationAndLoad(sbn: StatusBarNotification) {
         mediaDataManager.onNotificationAdded(KEY, sbn)
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 1, background = 1)
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -2435,8 +2461,8 @@
             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/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 3b541cd..c0f503d 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,33 @@
 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.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 +74,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.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 +112,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 +137,15 @@
 @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() {
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
 
     @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 +161,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 +170,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,13 +182,28 @@
     @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
 
-    private val settings = FakeSettings()
+    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 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 instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
 
     private val originalSmartspaceSetting =
@@ -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,10 @@
         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_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 +386,7 @@
             PACKAGE_NAME
         )
 
+        testScope.runCurrent()
         backgroundExecutor.runAllReady()
         foregroundExecutor.runAllReady()
         verify(listener)
@@ -389,7 +412,7 @@
     @Test
     fun testLoadsMetadataOnBackground() {
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
-        assertThat(backgroundExecutor.numPending()).isEqualTo(1)
+        testScope.assertRunAllReady(foreground = 0, background = 1)
     }
 
     @Test
@@ -406,8 +429,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 +446,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 +473,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 +518,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 +604,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 +634,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 +675,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 +783,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 +870,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 +901,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 +1020,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 +1047,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 +1072,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 +1098,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 +1116,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 +1135,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 +1149,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 +1168,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 +1235,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 +1352,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 +1378,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 +1412,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 +1444,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 +1477,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 +1495,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 +1516,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 +1624,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 +1659,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 +1678,6 @@
     @Test
     fun testPlaybackActions_noState_usesNotification() {
         val desc = "Notification Action"
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         whenever(controller.playbackState).thenReturn(null)
 
         val notifWithAction =
@@ -1683,8 +1692,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 +1711,6 @@
     @Test
     fun testPlaybackActions_hasPrevNext() {
         val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions =
             PlaybackState.ACTION_PLAY or
                 PlaybackState.ACTION_SKIP_TO_PREVIOUS or
@@ -1747,7 +1754,6 @@
     @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)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
@@ -1779,7 +1785,6 @@
 
     @Test
     fun testPlaybackActions_connecting() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder =
             PlaybackState.Builder()
@@ -1798,9 +1803,74 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_DRAWABLES_REUSE)
+    fun postWithPlaybackActions_drawablesReused() {
+        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.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() {
+        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.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)
         val stateActions = PlaybackState.ACTION_PLAY
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         customDesc.forEach {
@@ -1838,7 +1908,6 @@
 
     @Test
     fun testPlaybackActions_playPause_hasButton() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val stateActions = PlaybackState.ACTION_PLAY_PAUSE
         val stateBuilder = PlaybackState.Builder().setActions(stateActions)
         whenever(controller.playbackState).thenReturn(stateBuilder.build())
@@ -1875,8 +1944,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 +2006,6 @@
 
     @Test
     fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build()
         whenever(controller.playbackState).thenReturn(state)
 
@@ -1982,6 +2049,7 @@
             pendingIntent,
             PACKAGE_NAME
         )
+        testScope.runCurrent()
         backgroundExecutor.runAllReady()
         foregroundExecutor.runAllReady()
 
@@ -2060,7 +2128,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 +2158,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 +2176,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 +2193,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 +2225,7 @@
 
     @Test
     fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2177,8 +2244,7 @@
 
     @Test
     fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2211,8 +2277,7 @@
 
     @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)
         addPlaybackStateAction()
 
         // When a media control using session actions is added, and then the session is destroyed
@@ -2231,8 +2296,7 @@
 
     @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)
         addPlaybackStateAction()
 
         // When a media control using session actions and that does allow resumption is added,
@@ -2265,7 +2329,6 @@
 
     @Test
     fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         addPlaybackStateAction()
 
         // When a media control with PlaybackState actions is added, times out,
@@ -2292,7 +2355,6 @@
 
     @Test
     fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         addPlaybackStateAction()
 
         // When a media control using session actions is added, and then the session is destroyed
@@ -2311,7 +2373,6 @@
 
     @Test
     fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() {
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         addPlaybackStateAction()
 
         // When a media control using session actions and that does allow resumption is added,
@@ -2344,8 +2405,7 @@
 
     @Test
     fun testSessionDestroyed_noNotificationKey_stillRemoved() {
-        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
-        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        fakeFeatureFlags.set(MEDIA_RETAIN_SESSIONS, true)
 
         // When a notiifcation is added and then removed before it is fully processed
         mediaDataProcessor.onNotificationAdded(KEY, mediaNotification)
@@ -2416,6 +2476,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 +2501,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 +2535,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..03667cf 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,22 +27,28 @@
 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
+import com.android.systemui.kosmos.unconfinedTestDispatcher
 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
@@ -67,18 +73,19 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.unconfinedDispatcherFakeSettings
 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.test.TestDispatcher
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
@@ -106,11 +113,14 @@
 private const val PAUSED_LOCAL = "paused local"
 private const val PLAYING_LOCAL = "playing local"
 
+@ExperimentalCoroutinesApi
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidJUnit4::class)
 class MediaCarouselControllerTest : SysuiTestCase() {
-    val kosmos = testKosmos()
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.unconfinedTestDispatcher
+    private val secureSettings = kosmos.unconfinedDispatcherFakeSettings
 
     @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel>
     @Mock lateinit var mediaViewControllerFactory: Provider<MediaViewController>
@@ -132,7 +142,6 @@
     @Mock lateinit var mediaFlags: MediaFlags
     @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock lateinit var globalSettings: GlobalSettings
-    private lateinit var secureSettings: SecureSettings
     private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
     @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
     @Captor
@@ -144,7 +153,6 @@
 
     private val clock = FakeSystemClock()
     private lateinit var bgExecutor: FakeExecutor
-    private lateinit var testDispatcher: TestDispatcher
     private lateinit var mediaCarouselController: MediaCarouselController
 
     private var originalResumeSetting =
@@ -153,10 +161,8 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        secureSettings = FakeSettings()
         context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
         bgExecutor = FakeExecutor(clock)
-        testDispatcher = UnconfinedTestDispatcher()
         mediaCarouselController =
             MediaCarouselController(
                 applicationScope = kosmos.applicationCoroutineScope,
@@ -183,10 +189,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 +410,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 +447,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 +558,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 +618,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 +664,11 @@
         assertEquals(playerIndex, 0)
     }
 
+    @DisableSceneContainer
     @Test
     fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         var result = false
         mediaCarouselController.updateHostVisibility = { result = true }
 
@@ -658,8 +678,11 @@
         assertEquals(true, result)
     }
 
+    @DisableSceneContainer
     @Test
     fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
+        verify(mediaDataManager).addListener(capture(listener))
+
         var result = false
         mediaCarouselController.updateHostVisibility = { result = true }
 
@@ -788,8 +811,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 +830,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 +874,6 @@
     }
 
     @DisableSceneContainer
-    @ExperimentalCoroutinesApi
     @Test
     fun testKeyguardGone_showMediaCarousel() =
         kosmos.testScope.runTest {
@@ -869,7 +897,6 @@
         }
 
     @EnableSceneContainer
-    @ExperimentalCoroutinesApi
     @Test
     fun testKeyguardGone_showMediaCarousel_scene_container() =
         kosmos.testScope.runTest {
@@ -887,7 +914,6 @@
             job.cancel()
         }
 
-    @ExperimentalCoroutinesApi
     @Test
     fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() {
         kosmos.testScope.runTest {
@@ -917,7 +943,6 @@
         }
     }
 
-    @ExperimentalCoroutinesApi
     @Test
     fun keyguardShowing_allowedOnLockscreen_updateVisibility() {
         kosmos.testScope.runTest {
@@ -947,6 +972,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 +1116,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 +1137,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 +1166,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 +1199,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/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index f531a3f..3e3aa4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -16,9 +16,9 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
-import com.android.wm.shell.util.GroupedRecentTaskInfo
-import com.android.wm.shell.util.SplitBounds
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import java.util.function.Consumer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
index 8fbd3c8..69b7b2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -29,8 +29,8 @@
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.util.mockito.mock
+import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.splitscreen.SplitScreen
-import com.android.wm.shell.util.SplitBounds
 import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
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/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
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/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index c2f035f1..848c8db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -25,10 +25,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.settingslib.notification.data.repository.FakeZenModeRepository
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
@@ -36,21 +39,21 @@
 import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
 import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor
 import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
 import com.android.systemui.qs.tiles.impl.modes.ui.ModesTileMapper
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
 import com.android.systemui.res.R
-import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
-import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.After
@@ -59,7 +62,6 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -68,6 +70,9 @@
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 class ModesTileTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val testDispatcher = kosmos.testDispatcher
 
     @Mock private lateinit var qsHost: QSHost
 
@@ -85,25 +90,13 @@
 
     @Mock private lateinit var dialogDelegate: ModesDialogDelegate
 
-    private val testDispatcher = UnconfinedTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
-
     private val inputHandler = FakeQSTileIntentUserInputHandler()
-    private val zenModeRepository = FakeZenModeRepository()
+    private val zenModeRepository = kosmos.zenModeRepository
     private val tileDataInteractor =
-        ModesTileDataInteractor(
-            context,
-            ZenModeInteractor(zenModeRepository, mock<NotificationSettingsRepository>()),
-            testDispatcher
-        )
+        ModesTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher)
     private val mapper =
         ModesTileMapper(
-            context.orCreateTestableResources
-                .apply {
-                    addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable())
-                    addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable())
-                }
-                .resources,
+            context.resources,
             context.theme,
         )
 
@@ -127,7 +120,7 @@
                 QSTileConfigTestBuilder.build {
                     uiConfig =
                         QSTileUIConfig.Resource(
-                            iconRes = R.drawable.qs_dnd_icon_off,
+                            iconRes = ModesTile.ICON_RES_ID,
                             labelRes = R.string.quick_settings_modes_label,
                         )
                 }
@@ -179,4 +172,43 @@
 
             assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE)
         }
+
+    @Test
+    fun handleUpdateState_withTileModel_updatesState() =
+        testScope.runTest {
+            val tileState =
+                QSTile.State().apply {
+                    state = Tile.STATE_INACTIVE
+                    secondaryLabel = "Old secondary label"
+                }
+            val model =
+                ModesTileModel(
+                    isActivated = true,
+                    activeModes = listOf("One", "Two"),
+                    icon = TestStubDrawable().asIcon()
+                )
+
+            underTest.handleUpdateState(tileState, model)
+
+            assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE)
+            assertThat(tileState.secondaryLabel).isEqualTo("2 modes are active")
+        }
+
+    @Test
+    fun handleUpdateState_withNull_updatesState() =
+        testScope.runTest {
+            val tileState =
+                QSTile.State().apply {
+                    state = Tile.STATE_INACTIVE
+                    secondaryLabel = "Old secondary label"
+                }
+            zenModeRepository.addMode("One", active = true)
+            zenModeRepository.addMode("Two", active = true)
+            runCurrent()
+
+            underTest.handleUpdateState(tileState, null)
+
+            assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE)
+            assertThat(tileState.secondaryLabel).isEqualTo("2 modes are active")
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt
new file mode 100644
index 0000000..d8618fa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.recordissue
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC
+import com.android.traceur.PresetTraceConfigs
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+/**
+ * CustomTraceState is customized Traceur settings for power users. These settings determine what
+ * tracing is used during the Record Issue Quick Settings flow. This class tests that those features
+ * are persistently and accurately stored across sessions.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class CustomTraceStateTest : SysuiTestCase() {
+
+    private lateinit var underTest: CustomTraceState
+
+    @Before
+    fun setup() {
+        underTest = CustomTraceState(context.getSharedPreferences(TILE_SPEC, 0))
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun trace_config_is_stored_accurately() {
+        val expected = PresetTraceConfigs.getUiConfig()
+
+        underTest.traceConfig = expected
+
+        val actual = underTest.traceConfig
+        Truth.assertThat(actual.longTrace).isEqualTo(expected.longTrace)
+        Truth.assertThat(actual.maxLongTraceSizeMb).isEqualTo(expected.maxLongTraceSizeMb)
+        Truth.assertThat(actual.maxLongTraceDurationMinutes)
+            .isEqualTo(expected.maxLongTraceDurationMinutes)
+        Truth.assertThat(actual.apps).isEqualTo(expected.apps)
+        Truth.assertThat(actual.winscope).isEqualTo(expected.winscope)
+        Truth.assertThat(actual.attachToBugreport).isEqualTo(expected.attachToBugreport)
+        Truth.assertThat(actual.bufferSizeKb).isEqualTo(expected.bufferSizeKb)
+        Truth.assertThat(actual.tags).isEqualTo(expected.tags)
+    }
+
+    @Test
+    fun trace_config_is_persistently_stored_between_instances() {
+        val expected = PresetTraceConfigs.getUiConfig()
+
+        underTest.traceConfig = expected
+
+        val actual = CustomTraceState(context.getSharedPreferences(TILE_SPEC, 0)).traceConfig
+        Truth.assertThat(actual.longTrace).isEqualTo(expected.longTrace)
+        Truth.assertThat(actual.maxLongTraceSizeMb).isEqualTo(expected.maxLongTraceSizeMb)
+        Truth.assertThat(actual.maxLongTraceDurationMinutes)
+            .isEqualTo(expected.maxLongTraceDurationMinutes)
+        Truth.assertThat(actual.apps).isEqualTo(expected.apps)
+        Truth.assertThat(actual.winscope).isEqualTo(expected.winscope)
+        Truth.assertThat(actual.attachToBugreport).isEqualTo(expected.attachToBugreport)
+        Truth.assertThat(actual.bufferSizeKb).isEqualTo(expected.bufferSizeKb)
+        Truth.assertThat(actual.tags).isEqualTo(expected.tags)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt
new file mode 100644
index 0000000..4ab3c7b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.userFileManager
+import com.android.systemui.settings.userTracker
+import com.google.common.truth.Truth
+import java.util.concurrent.CountDownLatch
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class IssueRecordingStateTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+    private lateinit var underTest: IssueRecordingState
+
+    @Before
+    fun setup() {
+        underTest = IssueRecordingState(kosmos.userTracker, kosmos.userFileManager)
+    }
+
+    @Test
+    fun takeBugreport_isSaved_betweenDifferentSessions() {
+        val expected = true
+
+        underTest.takeBugreport = expected
+        underTest = IssueRecordingState(kosmos.userTracker, kosmos.userFileManager)
+
+        Truth.assertThat(underTest.takeBugreport).isEqualTo(expected)
+    }
+
+    @Test
+    fun recordScreen_isSaved_betweenDifferentSessions() {
+        val expected = true
+
+        underTest.recordScreen = expected
+        underTest = IssueRecordingState(kosmos.userTracker, kosmos.userFileManager)
+
+        Truth.assertThat(underTest.recordScreen).isEqualTo(expected)
+    }
+
+    @Test
+    fun hasUserApprovedScreenRecording_isTrue_afterBeingMarkedAsCompleted() {
+        underTest.markUserApprovalForScreenRecording()
+        underTest = IssueRecordingState(kosmos.userTracker, kosmos.userFileManager)
+
+        Truth.assertThat(underTest.hasUserApprovedScreenRecording).isEqualTo(true)
+    }
+
+    @Test
+    fun tagTitles_areSavedConsistently() {
+        val expected = setOf("a", "b", "c")
+
+        underTest.tagTitles = expected
+        underTest = IssueRecordingState(kosmos.userTracker, kosmos.userFileManager)
+
+        Truth.assertThat(underTest.tagTitles).isEqualTo(expected)
+    }
+
+    @Test
+    fun isRecording_callsListeners_onTheValueChanging() {
+        val count = CountDownLatch(1)
+        val listener = Runnable { count.countDown() }
+
+        underTest.addListener(listener)
+        underTest.isRecording = true
+
+        Truth.assertThat(count.count).isEqualTo(0)
+    }
+
+    @Test
+    fun isRecording_callsOnlyListeners_whoHaveNotBeenRemoved() {
+        val count1 = CountDownLatch(1)
+        val count2 = CountDownLatch(1)
+        val listener1 = Runnable { count1.countDown() }
+        val listener2 = Runnable { count2.countDown() }
+
+        underTest.addListener(listener1)
+        underTest.removeListener(listener1)
+        underTest.addListener(listener2)
+        underTest.isRecording = true
+
+        Truth.assertThat(count1.count).isEqualTo(1)
+        Truth.assertThat(count2.count).isEqualTo(0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
index b31d21e..15da77d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -2,8 +2,6 @@
 
 import android.graphics.drawable.Drawable
 import android.os.UserHandle
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
@@ -90,20 +88,6 @@
     }
 
     @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
-    fun testOnScreenshotTakenUserHandle_withWorkProfileFirstRun() {
-        whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle)))
-            .thenReturn(workProfileData)
-        messageContainer.onScreenshotTaken(screenshotData)
-
-        verify(workProfileMessageController)
-            .populateView(eq(workProfileFirstRunView), eq(workProfileData), any())
-        assertEquals(View.VISIBLE, workProfileFirstRunView.visibility)
-        assertEquals(View.GONE, detectionNoticeView.visibility)
-    }
-
-    @Test
-    @EnableFlags(com.android.systemui.Flags.FLAG_SCREENSHOT_PRIVATE_PROFILE_BEHAVIOR_FIX)
     fun testOnScreenshotTakenUserHandle_withProfileProfileFirstRun() = runTest {
         val profileData =
             ProfileMessageController.ProfileFirstRunData(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
deleted file mode 100644
index 0847f01..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2022 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.screenshot
-
-import android.content.ComponentName
-import android.graphics.Bitmap
-import android.graphics.ColorSpace
-import android.graphics.Insets
-import android.graphics.Rect
-import android.hardware.HardwareBuffer
-import android.os.UserHandle
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
-import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
-import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotRequest
-import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.runBlocking
-import org.junit.Assert
-import org.junit.Test
-
-private const val USER_ID = 1
-private const val TASK_ID = 1
-
-class RequestProcessorTest {
-    private val imageCapture = FakeImageCapture()
-    private val component = ComponentName("android.test", "android.test.Component")
-    private val bounds = Rect(25, 25, 75, 75)
-
-    private val policy = FakeScreenshotPolicy()
-
-    @Test
-    fun testFullScreenshot() = runBlocking {
-        // Indicate that the primary content belongs to a normal user
-        policy.setManagedProfile(USER_ID, false)
-        policy.setDisplayContentInfo(
-            policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
-        )
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
-        val processor = RequestProcessor(imageCapture, policy)
-
-        val processedData = processor.process(ScreenshotData.fromRequest(request))
-
-        // Request has topComponent added, but otherwise unchanged.
-        assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
-        assertThat(processedData.source).isEqualTo(SCREENSHOT_OTHER)
-        assertThat(processedData.topComponent).isEqualTo(component)
-    }
-
-    @Test
-    fun testFullScreenshot_managedProfile() = runBlocking {
-        // Provide a fake task bitmap when asked
-        val bitmap = makeHardwareBitmap(100, 100)
-        imageCapture.image = bitmap
-
-        // Indicate that the primary content belongs to a manged profile
-        policy.setManagedProfile(USER_ID, true)
-        policy.setDisplayContentInfo(
-            policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
-        )
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
-        val processor = RequestProcessor(imageCapture, policy)
-
-        val processedData = processor.process(ScreenshotData.fromRequest(request))
-
-        // Expect a task snapshot is taken, overriding the full screen mode
-        assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
-        assertThat(processedData.bitmap).isEqualTo(bitmap)
-        assertThat(processedData.screenBounds).isEqualTo(bounds)
-        assertThat(processedData.insets).isEqualTo(Insets.NONE)
-        assertThat(processedData.taskId).isEqualTo(TASK_ID)
-        assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
-    }
-
-    @Test
-    fun testFullScreenshot_managedProfile_nullBitmap() {
-        // Provide a null task bitmap when asked
-        imageCapture.image = null
-
-        // Indicate that the primary content belongs to a manged profile
-        policy.setManagedProfile(USER_ID, true)
-        policy.setDisplayContentInfo(
-            policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
-        )
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
-        val processor = RequestProcessor(imageCapture, policy)
-
-        Assert.assertThrows(IllegalStateException::class.java) {
-            runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
-        }
-    }
-
-    @Test
-    fun testProvidedImageScreenshot() = runBlocking {
-        val bounds = Rect(50, 50, 150, 150)
-        val processor = RequestProcessor(imageCapture, policy)
-
-        policy.setManagedProfile(USER_ID, false)
-
-        val bitmap = makeHardwareBitmap(100, 100)
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
-                .setTopComponent(component)
-                .setTaskId(TASK_ID)
-                .setUserId(USER_ID)
-                .setBitmap(bitmap)
-                .setBoundsOnScreen(bounds)
-                .setInsets(Insets.NONE)
-                .build()
-
-        val screenshotData = ScreenshotData.fromRequest(request)
-        val processedData = processor.process(screenshotData)
-
-        assertThat(processedData).isEqualTo(screenshotData)
-    }
-
-    @Test
-    fun testProvidedImageScreenshot_managedProfile() = runBlocking {
-        val bounds = Rect(50, 50, 150, 150)
-        val processor = RequestProcessor(imageCapture, policy)
-
-        // Indicate that the screenshot belongs to a manged profile
-        policy.setManagedProfile(USER_ID, true)
-
-        val bitmap = makeHardwareBitmap(100, 100)
-
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
-                .setTopComponent(component)
-                .setTaskId(TASK_ID)
-                .setUserId(USER_ID)
-                .setBitmap(bitmap)
-                .setBoundsOnScreen(bounds)
-                .setInsets(Insets.NONE)
-                .build()
-
-        val screenshotData = ScreenshotData.fromRequest(request)
-        val processedData = processor.process(screenshotData)
-
-        // Work profile, but already a task snapshot, so no changes
-        assertThat(processedData).isEqualTo(screenshotData)
-    }
-
-    private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
-        val buffer =
-            HardwareBuffer.create(
-                width,
-                height,
-                HardwareBuffer.RGBA_8888,
-                1,
-                HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
-            )
-        return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
-    }
-
-    private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
-        return bitmap.hardwareBuffer == this.hardwareBuffer && bitmap.colorSpace == this.colorSpace
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index a8d5008..eb1a04d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -308,6 +308,40 @@
         assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNotNull();
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
+    public void appClipsLaunched_backlinks_multipleBacklinksAvailable_duplicateName()
+            throws RemoteException {
+        // Set up mocking for multiple backlinks.
+        ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo();
+
+        ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo();
+        RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask();
+        int taskId2 = BACKLINKS_TASK_ID + 2;
+        runningTaskInfo2.taskId = taskId2;
+
+        when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
+                mDisplayIdCaptor.capture()))
+                .thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS, runningTaskInfo2));
+        when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(resolveInfo1,
+                resolveInfo1, resolveInfo1, resolveInfo2, resolveInfo2, resolveInfo2);
+        when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
+
+        // Using same AssistContent data for both tasks.
+        mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, BACKLINKS_TASK_ID);
+        mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, taskId2);
+
+        // Mocking complete, trigger backlinks.
+        launchActivity();
+        waitForIdleSync();
+
+        // Verify default backlink shown to user has the numerical suffix.
+        TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
+        assertThat(backlinksData.getText().toString()).isEqualTo(
+                mContext.getString(R.string.backlinks_duplicate_label_format,
+                        BACKLINKS_TASK_APP_NAME, 1));
+    }
+
     private void setUpMocksForBacklinks() throws RemoteException {
         when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
                 mDisplayIdCaptor.capture()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index a5fbfb5..be9fcc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -18,11 +18,13 @@
 
 import android.content.ComponentName
 import android.content.Context
+import android.content.res.Resources
 import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.R
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.screenshot.data.model.DisplayContentModel
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
@@ -57,6 +59,7 @@
 import org.mockito.Mock
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class WorkProfilePolicyTest {
@@ -66,12 +69,17 @@
     @JvmField @Rule(order = 2) val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
     @Mock lateinit var mContext: Context
+    @Mock lateinit var mResources: Resources
 
     private val kosmos = Kosmos()
     private lateinit var policy: WorkProfilePolicy
 
     @Before
     fun setUp() {
+        // Set desktop mode supported
+        whenever(mContext.resources).thenReturn(mResources)
+        whenever(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true)
+
         policy = WorkProfilePolicy(kosmos.profileTypeRepository, mContext)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index 774aa51..2e2ac3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -33,9 +33,11 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.TruthJUnit.assume
+import java.util.concurrent.Executor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -54,10 +56,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
-
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -71,27 +70,19 @@
         fun isBackgroundUserTrackerEnabled(): Iterable<Boolean> = listOf(true, false)
     }
 
-    @Mock
-    private lateinit var context: Context
+    @Mock private lateinit var context: Context
 
-    @Mock
-    private lateinit var userManager: UserManager
+    @Mock private lateinit var userManager: UserManager
 
-    @Mock
-    private lateinit var iActivityManager: IActivityManager
+    @Mock private lateinit var iActivityManager: IActivityManager
 
-    @Mock
-    private lateinit var userSwitchingReply: IRemoteCallback
+    @Mock private lateinit var userSwitchingReply: IRemoteCallback
 
-    @Mock(stubOnly = true)
-    private lateinit var dumpManager: DumpManager
+    @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
 
-    @Mock(stubOnly = true)
-    private lateinit var handler: Handler
+    @Mock(stubOnly = true) private lateinit var handler: Handler
 
-    @Parameterized.Parameter
-    @JvmField
-    var isBackgroundUserTrackerEnabled: Boolean = false
+    @Parameterized.Parameter @JvmField var isBackgroundUserTrackerEnabled: Boolean = false
 
     private val testScope = TestScope()
     private val testDispatcher = StandardTestDispatcher(testScope.testScheduler)
@@ -104,365 +95,379 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(context.userId).thenReturn(UserHandle.USER_SYSTEM)
-        `when`(context.user).thenReturn(UserHandle.SYSTEM)
-        `when`(context.createContextAsUser(any(), anyInt())).thenAnswer { invocation ->
+        whenever(context.userId).thenReturn(UserHandle.USER_SYSTEM)
+        whenever(context.user).thenReturn(UserHandle.SYSTEM)
+        whenever(context.createContextAsUser(any(), anyInt())).thenAnswer { invocation ->
             val user = invocation.getArgument<UserHandle>(0)
-            `when`(context.user).thenReturn(user)
-            `when`(context.userId).thenReturn(user.identifier)
+            whenever(context.user).thenReturn(user)
+            whenever(context.userId).thenReturn(user.identifier)
             context
         }
-        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+        whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
             val info = UserInfo(invocation.getArgument<Int>(0), "", UserInfo.FLAG_FULL)
             listOf(info)
         }
 
         featureFlags.set(Flags.USER_TRACKER_BACKGROUND_CALLBACKS, isBackgroundUserTrackerEnabled)
         tracker =
-                UserTrackerImpl(
-                        context,
-                        { featureFlags },
-                        userManager,
-                        iActivityManager,
-                        dumpManager,
-                        testScope.backgroundScope,
-                        testDispatcher,
-                        handler,
-                )
+            UserTrackerImpl(
+                context,
+                { featureFlags },
+                userManager,
+                iActivityManager,
+                dumpManager,
+                testScope.backgroundScope,
+                testDispatcher,
+                handler,
+            )
     }
 
+    @Test fun testNotInitialized() = testScope.runTest { assertThat(tracker.initialized).isFalse() }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserIdBeforeInitThrowsException() = testScope.runTest { tracker.userId }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest { tracker.userHandle }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserContextBeforeInitThrowsException() = testScope.runTest { tracker.userContext }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserContentResolverBeforeInitThrowsException() =
+        testScope.runTest { tracker.userContentResolver }
+
+    @Test(expected = IllegalStateException::class)
+    fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest { tracker.userProfiles }
+
     @Test
-    fun testNotInitialized() = testScope.runTest {
-        assertThat(tracker.initialized).isFalse()
-    }
+    fun testInitialize() =
+        testScope.runTest {
+            tracker.initialize(0)
 
-    @Test(expected = IllegalStateException::class)
-    fun testGetUserIdBeforeInitThrowsException() = testScope.runTest {
-        tracker.userId
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun testGetUserHandleBeforeInitThrowsException() = testScope.runTest {
-        tracker.userHandle
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun testGetUserContextBeforeInitThrowsException() = testScope.runTest {
-        tracker.userContext
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun testGetUserContentResolverBeforeInitThrowsException() = testScope.runTest {
-        tracker.userContentResolver
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun testGetUserProfilesBeforeInitThrowsException() = testScope.runTest {
-        tracker.userProfiles
-    }
+            assertThat(tracker.initialized).isTrue()
+        }
 
     @Test
-    fun testInitialize() = testScope.runTest {
-        tracker.initialize(0)
+    fun testReceiverRegisteredOnInitialize() =
+        testScope.runTest {
+            tracker.initialize(0)
 
-        assertThat(tracker.initialized).isTrue()
-    }
+            val captor = ArgumentCaptor.forClass(IntentFilter::class.java)
 
-    @Test
-    fun testReceiverRegisteredOnInitialize() = testScope.runTest {
-        tracker.initialize(0)
-
-        val captor = ArgumentCaptor.forClass(IntentFilter::class.java)
-
-        verify(context)
+            verify(context)
                 .registerReceiverForAllUsers(eq(tracker), capture(captor), isNull(), eq(handler))
-        with(captor.value) {
-            assertThat(countActions()).isEqualTo(11)
-            assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
-            assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
-            assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
-            assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
-            assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
-            assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
-            assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
-            assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
-            assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
-            assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
-            assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
-        }
-    }
-
-    @Test
-    fun testInitialValuesSet() = testScope.runTest {
-        val testID = 4
-        tracker.initialize(testID)
-
-        verify(userManager).getProfiles(testID)
-
-        assertThat(tracker.userId).isEqualTo(testID)
-        assertThat(tracker.userHandle).isEqualTo(UserHandle.of(testID))
-        assertThat(tracker.userContext.userId).isEqualTo(testID)
-        assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(testID))
-        assertThat(tracker.userProfiles).hasSize(1)
-
-        val info = tracker.userProfiles[0]
-        assertThat(info.id).isEqualTo(testID)
-    }
-
-    @Test
-    fun testUserSwitch() = testScope.runTest {
-        tracker.initialize(0)
-        val newID = 5
-
-        val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
-        verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
-        captor.value.onBeforeUserSwitching(newID)
-        captor.value.onUserSwitching(newID, userSwitchingReply)
-        runCurrent()
-        verify(userSwitchingReply).sendResult(any())
-
-        verify(userManager).getProfiles(newID)
-
-        assertThat(tracker.userId).isEqualTo(newID)
-        assertThat(tracker.userHandle).isEqualTo(UserHandle.of(newID))
-        assertThat(tracker.userContext.userId).isEqualTo(newID)
-        assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(newID))
-        assertThat(tracker.userProfiles).hasSize(1)
-
-        val info = tracker.userProfiles[0]
-        assertThat(info.id).isEqualTo(newID)
-    }
-
-    @Test
-    fun testManagedProfileAvailable() = testScope.runTest {
-        tracker.initialize(0)
-        val profileID = tracker.userId + 10
-
-        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
-            val id = invocation.getArgument<Int>(0)
-            val info = UserInfo(id, "", UserInfo.FLAG_FULL)
-            val infoProfile = UserInfo(
-                    id + 10,
-                    "",
-                    "",
-                    UserInfo.FLAG_MANAGED_PROFILE,
-                    UserManager.USER_TYPE_PROFILE_MANAGED
-            )
-            infoProfile.profileGroupId = id
-            listOf(info, infoProfile)
+            with(captor.value) {
+                assertThat(countActions()).isEqualTo(11)
+                assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
+                assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
+                assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
+                assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
+                assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
+                assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
+                assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
+                assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
+                assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
+                assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
+                assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
+            }
         }
 
-        val intent = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
-                .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
-        tracker.onReceive(context, intent)
-
-        assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
-    }
-
     @Test
-    fun testManagedProfileUnavailable() = testScope.runTest {
-        tracker.initialize(0)
-        val profileID = tracker.userId + 10
+    fun testInitialValuesSet() =
+        testScope.runTest {
+            val testID = 4
+            tracker.initialize(testID)
 
-        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
-            val id = invocation.getArgument<Int>(0)
-            val info = UserInfo(id, "", UserInfo.FLAG_FULL)
-            val infoProfile = UserInfo(
-                    id + 10,
-                    "",
-                    "",
-                    UserInfo.FLAG_MANAGED_PROFILE or UserInfo.FLAG_QUIET_MODE,
-                    UserManager.USER_TYPE_PROFILE_MANAGED
-            )
-            infoProfile.profileGroupId = id
-            listOf(info, infoProfile)
+            verify(userManager).getProfiles(testID)
+
+            assertThat(tracker.userId).isEqualTo(testID)
+            assertThat(tracker.userHandle).isEqualTo(UserHandle.of(testID))
+            assertThat(tracker.userContext.userId).isEqualTo(testID)
+            assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(testID))
+            assertThat(tracker.userProfiles).hasSize(1)
+
+            val info = tracker.userProfiles[0]
+            assertThat(info.id).isEqualTo(testID)
         }
 
-        val intent = Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
-                .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
-        tracker.onReceive(context, intent)
-
-        assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
-    }
-
     @Test
-    fun testManagedProfileStartedAndRemoved() = testScope.runTest {
-        tracker.initialize(0)
-        val profileID = tracker.userId + 10
+    fun testUserSwitch() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val newID = 5
 
-        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
-            val id = invocation.getArgument<Int>(0)
-            val info = UserInfo(id, "", UserInfo.FLAG_FULL)
-            val infoProfile = UserInfo(
-                    id + 10,
-                    "",
-                    "",
-                    UserInfo.FLAG_MANAGED_PROFILE,
-                    UserManager.USER_TYPE_PROFILE_MANAGED
-            )
-            infoProfile.profileGroupId = id
-            listOf(info, infoProfile)
+            val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+            verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+            captor.value.onBeforeUserSwitching(newID)
+            captor.value.onUserSwitching(newID, userSwitchingReply)
+            runCurrent()
+            verify(userSwitchingReply).sendResult(any())
+
+            verify(userManager).getProfiles(newID)
+
+            assertThat(tracker.userId).isEqualTo(newID)
+            assertThat(tracker.userHandle).isEqualTo(UserHandle.of(newID))
+            assertThat(tracker.userContext.userId).isEqualTo(newID)
+            assertThat(tracker.userContext.user).isEqualTo(UserHandle.of(newID))
+            assertThat(tracker.userProfiles).hasSize(1)
+
+            val info = tracker.userProfiles[0]
+            assertThat(info.id).isEqualTo(newID)
         }
 
-        // Managed profile started
-        val intent = Intent(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
-                .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
-        tracker.onReceive(context, intent)
+    @Test
+    fun testManagedProfileAvailable() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val profileID = tracker.userId + 10
 
-        assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId, profileID)
+            whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+                val id = invocation.getArgument<Int>(0)
+                val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+                val infoProfile =
+                    UserInfo(
+                        id + 10,
+                        "",
+                        "",
+                        UserInfo.FLAG_MANAGED_PROFILE,
+                        UserManager.USER_TYPE_PROFILE_MANAGED
+                    )
+                infoProfile.profileGroupId = id
+                listOf(info, infoProfile)
+            }
 
-        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
-            listOf(UserInfo(invocation.getArgument(0), "", UserInfo.FLAG_FULL))
+            val intent =
+                Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+                    .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+            tracker.onReceive(context, intent)
+
+            assertThat(tracker.userProfiles.map { it.id })
+                .containsExactly(tracker.userId, profileID)
         }
 
-        val intent2 = Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)
-                .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
-        tracker.onReceive(context, intent2)
-
-        assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId)
-    }
-
     @Test
-    fun testCallbackNotCalledOnAdd() = testScope.runTest {
-        tracker.initialize(0)
-        val callback = TestCallback()
+    fun testManagedProfileUnavailable() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val profileID = tracker.userId + 10
 
-        tracker.addCallback(callback, executor)
+            whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+                val id = invocation.getArgument<Int>(0)
+                val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+                val infoProfile =
+                    UserInfo(
+                        id + 10,
+                        "",
+                        "",
+                        UserInfo.FLAG_MANAGED_PROFILE or UserInfo.FLAG_QUIET_MODE,
+                        UserManager.USER_TYPE_PROFILE_MANAGED
+                    )
+                infoProfile.profileGroupId = id
+                listOf(info, infoProfile)
+            }
 
-        assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
-        assertThat(callback.calledOnUserChanged).isEqualTo(0)
-    }
+            val intent =
+                Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
+                    .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+            tracker.onReceive(context, intent)
 
-    @Test
-    fun testCallbackCalledOnUserChanging() = testScope.runTest {
-        tracker.initialize(0)
-        val callback = TestCallback()
-        tracker.addCallback(callback, executor)
-
-        val newID = 5
-
-        val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
-        verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
-        captor.value.onBeforeUserSwitching(newID)
-        captor.value.onUserSwitching(newID, userSwitchingReply)
-        runCurrent()
-
-        verify(userSwitchingReply).sendResult(any())
-        assertThat(callback.calledOnUserChanging).isEqualTo(1)
-        assertThat(callback.lastUser).isEqualTo(newID)
-        assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
-    }
-
-    @Test
-    fun testAsyncCallbackWaitsUserToChange() = testScope.runTest {
-        // Skip this test for CountDownLatch variation. The problem is that there would be a
-        // deadlock if the callbacks processing runs on the same thread as the callback (which
-        // is blocked by the latch). Before the change it works because the callbacks are
-        // processed on a binder thread which is always distinct.
-        // This is the issue that this feature addresses.
-        assume().that(isBackgroundUserTrackerEnabled).isTrue()
-
-        tracker.initialize(0)
-        val callback = TestCallback()
-        val callbackExecutor = FakeExecutor(FakeSystemClock())
-        tracker.addCallback(callback, callbackExecutor)
-
-        val newID = 5
-
-        val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
-        verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
-        captor.value.onUserSwitching(newID, userSwitchingReply)
-
-        assertThat(callback.calledOnUserChanging).isEqualTo(0)
-        verify(userSwitchingReply, never()).sendResult(any())
-
-        FakeExecutor.exhaustExecutors(callbackExecutor)
-        runCurrent()
-        FakeExecutor.exhaustExecutors(callbackExecutor)
-        runCurrent()
-
-        assertThat(callback.calledOnUserChanging).isEqualTo(1)
-        verify(userSwitchingReply).sendResult(any())
-    }
-
-    @Test
-    fun testCallbackCalledOnUserChanged() = testScope.runTest {
-        tracker.initialize(0)
-        val callback = TestCallback()
-        tracker.addCallback(callback, executor)
-
-        val newID = 5
-
-        val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
-        verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
-        captor.value.onBeforeUserSwitching(newID)
-        captor.value.onUserSwitchComplete(newID)
-        runCurrent()
-
-        assertThat(callback.calledOnUserChanged).isEqualTo(1)
-        assertThat(callback.lastUser).isEqualTo(newID)
-        assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
-        assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
-        assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(newID)
-    }
-
-    @Test
-    fun testCallbackCalledOnUserInfoChanged() = testScope.runTest {
-        tracker.initialize(0)
-        val callback = TestCallback()
-        tracker.addCallback(callback, executor)
-        val profileID = tracker.userId + 10
-
-        `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
-            val id = invocation.getArgument<Int>(0)
-            val info = UserInfo(id, "", UserInfo.FLAG_FULL)
-            val infoProfile = UserInfo(
-                    id + 10,
-                    "",
-                    "",
-                    UserInfo.FLAG_MANAGED_PROFILE,
-                    UserManager.USER_TYPE_PROFILE_MANAGED
-            )
-            infoProfile.profileGroupId = id
-            listOf(info, infoProfile)
+            assertThat(tracker.userProfiles.map { it.id })
+                .containsExactly(tracker.userId, profileID)
         }
 
-        val intent = Intent(Intent.ACTION_USER_INFO_CHANGED)
-                .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+    @Test
+    fun testManagedProfileStartedAndRemoved() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val profileID = tracker.userId + 10
 
-        tracker.onReceive(context, intent)
+            whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+                val id = invocation.getArgument<Int>(0)
+                val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+                val infoProfile =
+                    UserInfo(
+                        id + 10,
+                        "",
+                        "",
+                        UserInfo.FLAG_MANAGED_PROFILE,
+                        UserManager.USER_TYPE_PROFILE_MANAGED
+                    )
+                infoProfile.profileGroupId = id
+                listOf(info, infoProfile)
+            }
 
-        assertThat(callback.calledOnUserChanged).isEqualTo(0)
-        assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
-        assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
-    }
+            // Managed profile started
+            val intent =
+                Intent(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)
+                    .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+            tracker.onReceive(context, intent)
+
+            assertThat(tracker.userProfiles.map { it.id })
+                .containsExactly(tracker.userId, profileID)
+
+            whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+                listOf(UserInfo(invocation.getArgument(0), "", UserInfo.FLAG_FULL))
+            }
+
+            val intent2 =
+                Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED)
+                    .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+            tracker.onReceive(context, intent2)
+
+            assertThat(tracker.userProfiles.map { it.id }).containsExactly(tracker.userId)
+        }
 
     @Test
-    fun testCallbackRemoved() = testScope.runTest {
-        tracker.initialize(0)
-        val newID = 5
-        val profileID = newID + 10
+    fun testCallbackNotCalledOnAdd() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val callback = TestCallback()
 
-        val callback = TestCallback()
-        tracker.addCallback(callback, executor)
-        tracker.removeCallback(callback)
+            tracker.addCallback(callback, executor)
 
-        val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
-        verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
-        captor.value.onUserSwitching(newID, userSwitchingReply)
-        runCurrent()
-        verify(userSwitchingReply).sendResult(any())
-        captor.value.onUserSwitchComplete(newID)
+            assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
+            assertThat(callback.calledOnUserChanged).isEqualTo(0)
+        }
 
-        val intentProfiles = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
-                .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+    @Test
+    fun testCallbackCalledOnUserChanging() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val callback = TestCallback()
+            tracker.addCallback(callback, executor)
 
-        tracker.onReceive(context, intentProfiles)
+            val newID = 5
 
-        assertThat(callback.calledOnUserChanging).isEqualTo(0)
-        assertThat(callback.calledOnUserChanged).isEqualTo(0)
-        assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
-    }
+            val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+            verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+            captor.value.onBeforeUserSwitching(newID)
+            captor.value.onUserSwitching(newID, userSwitchingReply)
+            runCurrent()
+
+            verify(userSwitchingReply).sendResult(any())
+            assertThat(callback.calledOnUserChanging).isEqualTo(1)
+            assertThat(callback.lastUser).isEqualTo(newID)
+            assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+        }
+
+    @Test
+    fun testAsyncCallbackWaitsUserToChange() =
+        testScope.runTest {
+            // Skip this test for CountDownLatch variation. The problem is that there would be a
+            // deadlock if the callbacks processing runs on the same thread as the callback (which
+            // is blocked by the latch). Before the change it works because the callbacks are
+            // processed on a binder thread which is always distinct.
+            // This is the issue that this feature addresses.
+            assume().that(isBackgroundUserTrackerEnabled).isTrue()
+
+            tracker.initialize(0)
+            val callback = TestCallback()
+            val callbackExecutor = FakeExecutor(FakeSystemClock())
+            tracker.addCallback(callback, callbackExecutor)
+
+            val newID = 5
+
+            val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+            verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+            captor.value.onUserSwitching(newID, userSwitchingReply)
+
+            assertThat(callback.calledOnUserChanging).isEqualTo(0)
+            verify(userSwitchingReply, never()).sendResult(any())
+
+            FakeExecutor.exhaustExecutors(callbackExecutor)
+            runCurrent()
+            FakeExecutor.exhaustExecutors(callbackExecutor)
+            runCurrent()
+
+            assertThat(callback.calledOnUserChanging).isEqualTo(1)
+            verify(userSwitchingReply).sendResult(any())
+        }
+
+    @Test
+    fun testCallbackCalledOnUserChanged() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val callback = TestCallback()
+            tracker.addCallback(callback, executor)
+
+            val newID = 5
+
+            val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+            verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+            captor.value.onBeforeUserSwitching(newID)
+            captor.value.onUserSwitchComplete(newID)
+            runCurrent()
+
+            assertThat(callback.calledOnUserChanged).isEqualTo(1)
+            assertThat(callback.lastUser).isEqualTo(newID)
+            assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+            assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+            assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(newID)
+        }
+
+    @Test
+    fun testCallbackCalledOnUserInfoChanged() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val callback = TestCallback()
+            tracker.addCallback(callback, executor)
+            val profileID = tracker.userId + 10
+
+            whenever(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+                val id = invocation.getArgument<Int>(0)
+                val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+                val infoProfile =
+                    UserInfo(
+                        id + 10,
+                        "",
+                        "",
+                        UserInfo.FLAG_MANAGED_PROFILE,
+                        UserManager.USER_TYPE_PROFILE_MANAGED
+                    )
+                infoProfile.profileGroupId = id
+                listOf(info, infoProfile)
+            }
+
+            val intent =
+                Intent(Intent.ACTION_USER_INFO_CHANGED)
+                    .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+            tracker.onReceive(context, intent)
+
+            assertThat(callback.calledOnUserChanged).isEqualTo(0)
+            assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+            assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
+        }
+
+    @Test
+    fun testCallbackRemoved() =
+        testScope.runTest {
+            tracker.initialize(0)
+            val newID = 5
+            val profileID = newID + 10
+
+            val callback = TestCallback()
+            tracker.addCallback(callback, executor)
+            tracker.removeCallback(callback)
+
+            val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+            verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+            captor.value.onUserSwitching(newID, userSwitchingReply)
+            runCurrent()
+            verify(userSwitchingReply).sendResult(any())
+            captor.value.onUserSwitchComplete(newID)
+
+            val intentProfiles =
+                Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+                    .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+            tracker.onReceive(context, intentProfiles)
+
+            assertThat(callback.calledOnUserChanging).isEqualTo(0)
+            assertThat(callback.calledOnUserChanged).isEqualTo(0)
+            assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
+        }
 
     private class TestCallback : UserTracker.Callback {
         var calledOnUserChanging = 0
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..c0444fe 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,10 +57,10 @@
 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
-import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.lockscreen.lockscreenSmartspaceController
 import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
@@ -118,7 +117,8 @@
             object : AmbientTouchComponent.Factory {
                 override fun create(
                     lifecycleOwner: LifecycleOwner,
-                    touchHandlers: Set<TouchHandler>
+                    touchHandlers: Set<TouchHandler>,
+                    loggingName: String
                 ): AmbientTouchComponent =
                     object : AmbientTouchComponent {
                         override fun getTouchMonitor(): TouchMonitor = touchMonitor
@@ -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 {
@@ -796,7 +706,7 @@
                 verify(containerView).onTouchEvent(DOWN_EVENT)
 
                 // User is interacting with shade on lockscreen.
-                fakeShadeRepository.setLegacyLockscreenShadeTracking(true)
+                shadeTestUtil.setLockscreenShadeTracking(true)
                 testableLooper.processAllMessages()
 
                 // A move event is ignored while the user is already interacting.
@@ -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.
+                shadeTestUtil.setShadeExpansion(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/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 505f799..3f6617b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -197,6 +197,7 @@
                 () -> sceneInteractor,
                 () -> mKosmos.getFromGoneTransitionInteractor(),
                 () -> mKosmos.getFromLockscreenTransitionInteractor(),
+                () -> mKosmos.getFromOccludedTransitionInteractor(),
                 () -> mKosmos.getSharedNotificationContainerInteractor(),
                 mTestScope);
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index fadb1d7..b65a902 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -156,6 +156,25 @@
         }
 
     @Test
+    fun qsFullscreen_falseWhenIdleSplitShadeQs() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.isQsFullscreen)
+
+            // WHEN split shade is enabled and Idle on QuickSettings scene
+            shadeTestUtil.setSplitShade(true)
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(Scenes.QuickSettings)
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN QS is not fullscreen
+            Truth.assertThat(actual).isFalse()
+        }
+
+    @Test
     fun qsFullscreen_trueWhenIdleQS() =
         testScope.runTest {
             val actual by collectLastValue(underTest.isQsFullscreen)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
index 396d017..d6b3b91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.tuner.TunerService
@@ -71,7 +71,6 @@
 
     private val airplaneModeRepository = FakeAirplaneModeRepository()
     private val connectivityRepository = FakeConnectivityRepository()
-    private val mobileConnectionsRepository = FakeMobileConnectionsRepository()
 
     @Before
     fun setup() {
@@ -81,7 +80,7 @@
             AirplaneModeInteractor(
                 airplaneModeRepository,
                 connectivityRepository,
-                mobileConnectionsRepository,
+                kosmos.fakeMobileConnectionsRepository,
             )
 
         underTest =
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..60a1855 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,11 +42,16 @@
 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;
+import android.widget.ImageView;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -191,6 +197,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;
@@ -398,6 +432,32 @@
                 mIconView.getIconScale(), 0.01f);
     }
 
+    @Test
+    @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+    public void set_iconThatWantsFixedSpace_setsScaleType() {
+        mIconView.setScaleType(ImageView.ScaleType.FIT_START);
+        StatusBarIcon icon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+                Icon.createWithResource(mContext, R.drawable.ic_android), 0, 0, "",
+                StatusBarIcon.Type.SystemIcon, StatusBarIcon.Shape.FIXED_SPACE);
+
+        mIconView.set(icon);
+
+        assertThat(mIconView.getScaleType()).isEqualTo(ImageView.ScaleType.FIT_CENTER);
+    }
+
+    @Test
+    @EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
+    public void set_iconWithOtherShape_keepsScaleType() {
+        mIconView.setScaleType(ImageView.ScaleType.FIT_START);
+        StatusBarIcon icon = new StatusBarIcon(UserHandle.ALL, "mockPackage",
+                Icon.createWithResource(mContext, R.drawable.ic_android), 0, 0, "",
+                StatusBarIcon.Type.SystemIcon, StatusBarIcon.Shape.WRAP_CONTENT);
+
+        mIconView.set(icon);
+
+        assertThat(mIconView.getScaleType()).isEqualTo(ImageView.ScaleType.FIT_START);
+    }
+
     private static StatusBarNotification getMockSbn() {
         StatusBarNotification sbn = mock(StatusBarNotification.class);
         when(sbn.getNotification()).thenReturn(mock(Notification.class));
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..118dea6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
@@ -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.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.ColorsModel
+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_supportsColor() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            commandRegistry.onShellCommand(
+                pw,
+                arrayOf("demo-ron", "-p", "com.android.systemui", "-c", "#434343")
+            )
+
+            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+            assertThat((latest as OngoingActivityChipModel.Shown).colors)
+                .isInstanceOf(ColorsModel.Custom::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..f528ebb 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,72 +101,75 @@
     @Before
     fun setUp() {
         setUpPackageManagerForMediaProjection(kosmos)
+        kosmos.demoRonChipViewModel.start()
+        whenever(kosmos.packageManager.getApplicationIcon(any<String>()))
+            .thenReturn(BitmapDrawable())
     }
 
     @Test
-    fun chip_allHidden_hidden() =
+    fun primaryChip_allHidden_hidden() =
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.DoingNothing
             mediaProjectionState.value = MediaProjectionState.NotProjecting
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
 
-            val latest by collectLastValue(underTest.chip)
+            val latest by collectLastValue(underTest.primaryChip)
 
             assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
     @Test
-    fun chip_screenRecordShow_restHidden_screenRecordShown() =
+    fun primaryChip_screenRecordShow_restHidden_screenRecordShown() =
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionState.value = MediaProjectionState.NotProjecting
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
 
-            val latest by collectLastValue(underTest.chip)
+            val latest by collectLastValue(underTest.primaryChip)
 
             assertIsScreenRecordChip(latest)
         }
 
     @Test
-    fun chip_screenRecordShowAndCallShow_screenRecordShown() =
+    fun primaryChip_screenRecordShowAndCallShow_screenRecordShown() =
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
 
             callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
 
-            val latest by collectLastValue(underTest.chip)
+            val latest by collectLastValue(underTest.primaryChip)
 
             assertIsScreenRecordChip(latest)
         }
 
     @Test
-    fun chip_screenRecordShowAndShareToAppShow_screenRecordShown() =
+    fun primaryChip_screenRecordShowAndShareToAppShow_screenRecordShown() =
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
 
-            val latest by collectLastValue(underTest.chip)
+            val latest by collectLastValue(underTest.primaryChip)
 
             assertIsScreenRecordChip(latest)
         }
 
     @Test
-    fun chip_shareToAppShowAndCallShow_shareToAppShown() =
+    fun primaryChip_shareToAppShowAndCallShow_shareToAppShown() =
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.DoingNothing
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
             callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
 
-            val latest by collectLastValue(underTest.chip)
+            val latest by collectLastValue(underTest.primaryChip)
 
             assertIsShareToAppChip(latest)
         }
 
     @Test
-    fun chip_screenRecordAndShareToAppAndCastToOtherHideAndCallShown_callShown() =
+    fun primaryChip_screenRecordAndShareToAppAndCastToOtherHideAndCallShown_callShown() =
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.DoingNothing
             // MediaProjection covers both share-to-app and cast-to-other-device
@@ -163,21 +177,30 @@
 
             callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
 
-            val latest by collectLastValue(underTest.chip)
+            val latest by collectLastValue(underTest.primaryChip)
 
             assertIsCallChip(latest)
         }
 
     @Test
-    fun chip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    fun primaryChip_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)
+            val latest by collectLastValue(underTest.primaryChip)
 
+            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,16 +222,17 @@
         }
 
     @Test
-    fun chip_highestPriorityChipRemoved_showsNextPriorityChip() =
+    @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS)
+    fun primaryChip_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)
+            val latest by collectLastValue(underTest.primaryChip)
 
             // THEN the highest priority screen record is used
             assertIsScreenRecordChip(latest)
@@ -224,15 +248,21 @@
 
             // 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. */
     @Test
-    fun chip_timerDoesNotResetAfterSubscribersRestart() =
+    fun primaryChip_timerDoesNotResetAfterSubscribersRestart() =
         testScope.runTest {
             var latest: OngoingActivityChipModel? = null
 
-            val job1 = underTest.chip.onEach { latest = it }.launchIn(this)
+            val job1 = underTest.primaryChip.onEach { latest = it }.launchIn(this)
 
             // Start a chip with a timer
             systemClock.setElapsedRealtime(1234)
@@ -249,7 +279,7 @@
             systemClock.setElapsedRealtime(5678)
 
             // WHEN we re-subscribe to the chip flow
-            val job2 = underTest.chip.onEach { latest = it }.launchIn(this)
+            val job2 = underTest.primaryChip.onEach { latest = it }.launchIn(this)
 
             runCurrent()
 
@@ -260,13 +290,13 @@
         }
 
     @Test
-    fun chip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
+    fun primaryChip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
         testScope.runTest {
             screenRecordState.value = ScreenRecordModel.Recording
             mediaProjectionState.value = MediaProjectionState.NotProjecting
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
 
-            val latest by collectLastValue(underTest.chip)
+            val latest by collectLastValue(underTest.primaryChip)
 
             assertIsScreenRecordChip(latest)
 
@@ -280,14 +310,14 @@
         }
 
     @Test
-    fun chip_projectionStoppedViaDialog_chipHiddenWithoutAnimation() =
+    fun primaryChip_projectionStoppedViaDialog_chipHiddenWithoutAnimation() =
         testScope.runTest {
             mediaProjectionState.value =
                 MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
             screenRecordState.value = ScreenRecordModel.DoingNothing
             callRepo.setOngoingCallState(OngoingCallModel.NoCall)
 
-            val latest by collectLastValue(underTest.chip)
+            val latest by collectLastValue(underTest.primaryChip)
 
             assertIsShareToAppChip(latest)
 
@@ -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/commandline/ValueParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
index 8cf7473..1efb3f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt
@@ -40,6 +40,15 @@
     }
 
     @Test
+    fun parseColor() {
+        assertThat(Type.Color.parseValue("#434343").isSuccess).isTrue()
+        assertThat(Type.Color.parseValue("#aa123456").isSuccess).isTrue()
+        assertThat(Type.Color.parseValue("red").isSuccess).isTrue()
+
+        assertThat(Type.Color.parseValue("not a color").isFailure).isTrue()
+    }
+
+    @Test
     fun mapToComplexType() {
         val parseSquare = Type.Int.map { Rect(it, it, it, it) }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index ad6aca1..3c583f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
@@ -45,8 +46,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -66,6 +67,7 @@
         SensitiveNotificationProtectionController
     @Mock private lateinit var stackController: NotifStackController
     @Mock private lateinit var section: NotifSection
+    @Mock private lateinit var row: ExpandableNotificationRow
 
     @Before
     fun setUp() {
@@ -74,6 +76,8 @@
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)
 
         entry = NotificationEntryBuilder().setSection(section).build()
+        entry.row = row
+        entry.setSensitive(false, false)
         coordinator =
             StackCoordinator(
                 groupExpansionManagerImpl,
@@ -189,4 +193,17 @@
             .setNotifStats(NotifStats(1, false, false, true, false))
         verifyZeroInteractions(stackController)
     }
+
+    @Test
+    @EnableFlags(
+        FooterViewRefactor.FLAG_NAME
+    )
+    fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() {
+        entry.setSensitive(true, true)
+        whenever(section.bucket).thenReturn(BUCKET_ALERTING)
+        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        verify(activeNotificationsInteractor)
+            .setNotifStats(NotifStats(1, hasNonClearableAlertingNotifs = true, false, false, false))
+        verifyZeroInteractions(stackController)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index d1b1f46..ed99705 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -98,16 +98,20 @@
     // instead of VisualInterruptionDecisionProviderTestBase
     // because avalanche code is based on the suppression refactor.
 
+    private fun getAvalancheSuppressor() : AvalancheSuppressor {
+        return AvalancheSuppressor(
+            avalancheProvider, systemClock, settingsInteractor, packageManager,
+            uiEventLogger, context, notificationManager, logger
+        )
+    }
+
     @Test
     fun testAvalancheFilter_suppress_hasNotSeenEdu_showEduHun() {
         setAllowedEmergencyPkg(false)
         whenever(avalancheProvider.timeoutMs).thenReturn(20)
         whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
 
-        val avalancheSuppressor = AvalancheSuppressor(
-            avalancheProvider, systemClock, settingsInteractor, packageManager,
-            uiEventLogger, context, notificationManager
-        )
+        val avalancheSuppressor = getAvalancheSuppressor()
         avalancheSuppressor.hasSeenEdu = false
 
         withFilter(avalancheSuppressor) {
@@ -128,10 +132,7 @@
         whenever(avalancheProvider.timeoutMs).thenReturn(20)
         whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
 
-        val avalancheSuppressor = AvalancheSuppressor(
-            avalancheProvider, systemClock, settingsInteractor, packageManager,
-            uiEventLogger, context, notificationManager
-        )
+        val avalancheSuppressor = getAvalancheSuppressor()
         avalancheSuppressor.hasSeenEdu = true
 
         withFilter(avalancheSuppressor) {
@@ -151,8 +152,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                    uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -171,8 +171,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                    uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldNotHeadsUp(
@@ -191,8 +190,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                    uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -209,8 +207,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                    uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -227,8 +224,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                    uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -245,8 +241,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                    uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -263,8 +258,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -281,8 +275,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -300,8 +293,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -318,8 +310,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                    uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             assertFsiNotSuppressed()
         }
@@ -330,8 +321,7 @@
         avalancheProvider.startTime = whenAgo(10)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                    uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
@@ -359,8 +349,7 @@
         setAllowedEmergencyPkg(true)
 
         withFilter(
-            AvalancheSuppressor(avalancheProvider, systemClock, settingsInteractor, packageManager,
-                    uiEventLogger, context, notificationManager)
+            getAvalancheSuppressor()
         ) {
             ensurePeekState()
             assertShouldHeadsUp(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 9d3d9c1..284efc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -139,6 +139,7 @@
     protected val settingsInteractor: NotificationSettingsInteractor = mock()
     protected val packageManager: PackageManager = mock()
     protected val notificationManager: NotificationManager = mock()
+    protected val logger: VisualInterruptionDecisionLogger = mock()
     protected abstract val provider: VisualInterruptionDecisionProvider
 
     private val neverSuppresses = object : NotificationInterruptSuppressor {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 491919a..30a1214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -35,6 +35,9 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+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.NotificationRowContentBinder.BindParams
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -74,6 +77,7 @@
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
 import org.mockito.kotlin.whenever
 
 @SmallTest
@@ -125,8 +129,10 @@
             ): RichOngoingContentModel? = fakeRonContentModel
         }
 
-    private var fakeRonViewHolder: InflatedContentViewHolder? = null
-    private val fakeRonViewInflater =
+    private var fakeContractedRonViewHolder: ContentViewInflationResult = NullContentView
+    private var fakeExpandedRonViewHolder: ContentViewInflationResult = NullContentView
+    private var fakeHeadsUpRonViewHolder: ContentViewInflationResult = NullContentView
+    private var fakeRonViewInflater =
         spy(
             object : RichOngoingNotificationViewInflater {
                 override fun inflateView(
@@ -134,8 +140,20 @@
                     existingView: View?,
                     entry: NotificationEntry,
                     systemUiContext: Context,
-                    parentView: ViewGroup
-                ): InflatedContentViewHolder? = fakeRonViewHolder
+                    parentView: ViewGroup,
+                    viewType: RichOngoingNotificationViewType
+                ): ContentViewInflationResult =
+                    when (viewType) {
+                        RichOngoingNotificationViewType.Contracted -> fakeContractedRonViewHolder
+                        RichOngoingNotificationViewType.Expanded -> fakeExpandedRonViewHolder
+                        RichOngoingNotificationViewType.HeadsUp -> fakeHeadsUpRonViewHolder
+                    }
+
+                override fun canKeepView(
+                    contentModel: RichOngoingContentModel,
+                    existingView: View?,
+                    viewType: RichOngoingNotificationViewType
+                ): Boolean = false
             }
         )
 
@@ -149,6 +167,7 @@
                 .setContentText("Text")
                 .setStyle(Notification.BigTextStyle().bigText("big text"))
         testHelper = NotificationTestHelper(mContext, mDependency)
+        testHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL)
         row = spy(testHelper.createRow(builder.build()))
         notificationInflater =
             NotificationRowContentBinderImpl(
@@ -388,15 +407,62 @@
     @Test
     fun testRonModelRequiredForRonView() {
         fakeRonContentModel = null
-        val ronView = View(context)
-        fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
+        val contractedRonView = View(context)
+        val expandedRonView = View(context)
+        val headsUpRonView = View(context)
+        fakeContractedRonViewHolder =
+            InflatedContentViewHolder(view = contractedRonView, binder = mock())
+        fakeExpandedRonViewHolder =
+            InflatedContentViewHolder(view = expandedRonView, binder = mock())
+        fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = headsUpRonView, binder = mock())
+
         // WHEN inflater inflates
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
-        verify(fakeRonViewInflater, never()).inflateView(any(), any(), any(), any(), any())
+        val contentToInflate =
+            FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
+        inflateAndWait(notificationInflater, contentToInflate, row)
+        verifyZeroInteractions(fakeRonViewInflater)
     }
 
     @Test
-    fun testRonModelTriggersInflationOfRonView() {
+    fun testRonModelCleansUpRemoteViews() {
+        val ronView = View(context)
+
+        val entry = row.entry
+
+        fakeRonContentModel = mock<TimerContentModel>()
+        fakeContractedRonViewHolder =
+            InflatedContentViewHolder(view = ronView, binder = mock<DeferredContentViewBinder>())
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+        // VERIFY
+        verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_CONTRACTED))
+        verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_EXPANDED))
+        verify(cache).removeCachedView(eq(entry), eq(FLAG_CONTENT_VIEW_HEADS_UP))
+    }
+
+    @Test
+    fun testRonModelCleansUpSmartReplies() {
+        val ronView = View(context)
+
+        val privateLayout = spy(row.privateLayout)
+
+        row.privateLayout = privateLayout
+
+        fakeRonContentModel = mock<TimerContentModel>()
+        fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mock())
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+        // VERIFY
+        verify(privateLayout).setExpandedInflatedSmartReplies(eq(null))
+        verify(privateLayout).setHeadsUpInflatedSmartReplies(eq(null))
+    }
+
+    @Test
+    fun testRonModelTriggersInflationOfContractedRonView() {
         val mockRonModel = mock<TimerContentModel>()
         val ronView = View(context)
         val mockBinder = mock<DeferredContentViewBinder>()
@@ -405,18 +471,229 @@
         val privateLayout = row.privateLayout
 
         fakeRonContentModel = mockRonModel
-        fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+        fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
         // WHEN inflater inflates
         inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
         // VERIFY that the inflater is invoked
         verify(fakeRonViewInflater)
-            .inflateView(eq(mockRonModel), any(), eq(entry), any(), eq(privateLayout))
+            .inflateView(
+                eq(mockRonModel),
+                any(),
+                eq(entry),
+                any(),
+                eq(privateLayout),
+                eq(RichOngoingNotificationViewType.Contracted)
+            )
         assertThat(row.privateLayout.contractedChild).isSameInstanceAs(ronView)
         verify(mockBinder).setupContentViewBinder()
     }
 
     @Test
-    fun ronViewAppliesElementsInOrder() {
+    fun testRonModelTriggersInflationOfExpandedRonView() {
+        val mockRonModel = mock<TimerContentModel>()
+        val ronView = View(context)
+        val mockBinder = mock<DeferredContentViewBinder>()
+
+        val entry = row.entry
+        val privateLayout = row.privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+        // VERIFY that the inflater is invoked
+        verify(fakeRonViewInflater)
+            .inflateView(
+                eq(mockRonModel),
+                any(),
+                eq(entry),
+                any(),
+                eq(privateLayout),
+                eq(RichOngoingNotificationViewType.Expanded)
+            )
+        assertThat(row.privateLayout.expandedChild).isSameInstanceAs(ronView)
+        verify(mockBinder).setupContentViewBinder()
+    }
+
+    @Test
+    fun testRonModelTriggersInflationOfHeadsUpRonView() {
+        val mockRonModel = mock<TimerContentModel>()
+        val ronView = View(context)
+        val mockBinder = mock<DeferredContentViewBinder>()
+
+        val entry = row.entry
+        val privateLayout = row.privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+        // VERIFY that the inflater is invoked
+        verify(fakeRonViewInflater)
+            .inflateView(
+                eq(mockRonModel),
+                any(),
+                eq(entry),
+                any(),
+                eq(privateLayout),
+                eq(RichOngoingNotificationViewType.HeadsUp)
+            )
+        assertThat(row.privateLayout.headsUpChild).isSameInstanceAs(ronView)
+        verify(mockBinder).setupContentViewBinder()
+    }
+
+    @Test
+    fun keepExistingViewForContractedRonNotChangingContractedChild() {
+        val oldHandle = mock<DisposableHandle>()
+        val mockRonModel = mock<TimerContentModel>()
+
+        row.privateLayout.mContractedBinderHandle = oldHandle
+        val entry = spy(row.entry)
+        row.entry = entry
+        val privateLayout = spy(row.privateLayout)
+        row.privateLayout = privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeContractedRonViewHolder = KeepExistingView
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+        // THEN  do not dispose old contracted binder handle and change contracted child
+        verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+        verifyZeroInteractions(oldHandle)
+        verify(privateLayout, never()).setContractedChild(any())
+    }
+
+    @Test
+    fun keepExistingViewForExpandedRonNotChangingExpandedChild() {
+        val oldHandle = mock<DisposableHandle>()
+        val mockRonModel = mock<TimerContentModel>()
+
+        row.privateLayout.mExpandedBinderHandle = oldHandle
+        val entry = spy(row.entry)
+        row.entry = entry
+        val privateLayout = spy(row.privateLayout)
+        row.privateLayout = privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeExpandedRonViewHolder = KeepExistingView
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+        // THEN  do not dispose old expanded binder handle and change expanded child
+        verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+        verifyZeroInteractions(oldHandle)
+        verify(privateLayout, never()).setExpandedChild(any())
+    }
+
+    @Test
+    fun keepExistingViewForHeadsUpRonNotChangingHeadsUpChild() {
+        val oldHandle = mock<DisposableHandle>()
+        val mockRonModel = mock<TimerContentModel>()
+
+        row.privateLayout.mHeadsUpBinderHandle = oldHandle
+        val entry = spy(row.entry)
+        row.entry = entry
+        val privateLayout = spy(row.privateLayout)
+        row.privateLayout = privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeHeadsUpRonViewHolder = KeepExistingView
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+        // THEN - do not dispose old heads up binder handle and change heads up child
+        verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+        verifyZeroInteractions(oldHandle)
+        verify(privateLayout, never()).setHeadsUpChild(any())
+    }
+
+    @Test
+    fun nullContentViewForContractedRonAppliesElementsInOrder() {
+        val oldHandle = mock<DisposableHandle>()
+        val mockRonModel = mock<TimerContentModel>()
+
+        row.privateLayout.mContractedBinderHandle = oldHandle
+        val entry = spy(row.entry)
+        row.entry = entry
+        val privateLayout = spy(row.privateLayout)
+        row.privateLayout = privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeContractedRonViewHolder = NullContentView
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+
+        // Validate that these 4 steps happen in this precise order
+        inOrder(oldHandle, entry, privateLayout, cache) {
+            verify(oldHandle).dispose()
+            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+            verify(privateLayout).setContractedChild(eq(null))
+        }
+    }
+
+    @Test
+    fun nullContentViewForExpandedRonAppliesElementsInOrder() {
+        val oldHandle = mock<DisposableHandle>()
+        val mockRonModel = mock<TimerContentModel>()
+
+        row.privateLayout.mExpandedBinderHandle = oldHandle
+        val entry = spy(row.entry)
+        row.entry = entry
+        val privateLayout = spy(row.privateLayout)
+        row.privateLayout = privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeExpandedRonViewHolder = NullContentView
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+        // Validate that these 4 steps happen in this precise order
+        inOrder(oldHandle, entry, privateLayout, cache) {
+            verify(oldHandle).dispose()
+            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+            verify(privateLayout).setExpandedChild(eq(null))
+        }
+    }
+
+    @Test
+    fun nullContentViewForHeadsUpRonAppliesElementsInOrder() {
+        val oldHandle = mock<DisposableHandle>()
+        val mockRonModel = mock<TimerContentModel>()
+
+        row.privateLayout.mHeadsUpBinderHandle = oldHandle
+        val entry = spy(row.entry)
+        row.entry = entry
+        val privateLayout = spy(row.privateLayout)
+        row.privateLayout = privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeHeadsUpRonViewHolder = NullContentView
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+        // Validate that these 4 steps happen in this precise order
+        inOrder(oldHandle, entry, privateLayout, cache) {
+            verify(oldHandle).dispose()
+            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+            verify(privateLayout).setHeadsUpChild(eq(null))
+        }
+    }
+
+    @Test
+    fun contractedRonViewAppliesElementsInOrder() {
         val oldHandle = mock<DisposableHandle>()
         val mockRonModel = mock<TimerContentModel>()
         val ronView = View(context)
@@ -429,7 +706,8 @@
         row.privateLayout = privateLayout
 
         fakeRonContentModel = mockRonModel
-        fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+        fakeContractedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
         // WHEN inflater inflates
         inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
 
@@ -443,16 +721,89 @@
     }
 
     @Test
-    fun testRonNotReinflating() {
-        val handle0 = mock<DisposableHandle>()
-        val handle1 = mock<DisposableHandle>()
+    fun expandedRonViewAppliesElementsInOrder() {
+        val oldHandle = mock<DisposableHandle>()
+        val mockRonModel = mock<TimerContentModel>()
         val ronView = View(context)
+        val mockBinder = mock<DeferredContentViewBinder>()
+
+        row.privateLayout.mExpandedBinderHandle = oldHandle
+        val entry = spy(row.entry)
+        row.entry = entry
+        val privateLayout = spy(row.privateLayout)
+        row.privateLayout = privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeExpandedRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_EXPANDED, row)
+
+        // Validate that these 4 steps happen in this precise order
+        inOrder(oldHandle, entry, privateLayout, mockBinder) {
+            verify(oldHandle).dispose()
+            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+            verify(privateLayout).setExpandedChild(eq(ronView))
+            verify(mockBinder).setupContentViewBinder()
+        }
+    }
+
+    @Test
+    fun headsUpRonViewAppliesElementsInOrder() {
+        val oldHandle = mock<DisposableHandle>()
+        val mockRonModel = mock<TimerContentModel>()
+        val ronView = View(context)
+        val mockBinder = mock<DeferredContentViewBinder>()
+
+        row.privateLayout.mHeadsUpBinderHandle = oldHandle
+        val entry = spy(row.entry)
+        row.entry = entry
+        val privateLayout = spy(row.privateLayout)
+        row.privateLayout = privateLayout
+
+        fakeRonContentModel = mockRonModel
+        fakeHeadsUpRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder)
+
+        // WHEN inflater inflates
+        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, row)
+
+        // Validate that these 4 steps happen in this precise order
+        inOrder(oldHandle, entry, privateLayout, mockBinder) {
+            verify(oldHandle).dispose()
+            verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
+            verify(privateLayout).setHeadsUpChild(eq(ronView))
+            verify(mockBinder).setupContentViewBinder()
+        }
+    }
+
+    @Test
+    fun testRonNotReinflating() {
+        val oldContractedBinderHandle = mock<DisposableHandle>()
+        val oldExpandedBinderHandle = mock<DisposableHandle>()
+        val oldHeadsUpBinderHandle = mock<DisposableHandle>()
+
+        val contractedBinderHandle = mock<DisposableHandle>()
+        val expandedBinderHandle = mock<DisposableHandle>()
+        val headsUpBinderHandle = mock<DisposableHandle>()
+
+        val contractedRonView = View(context)
+        val expandedRonView = View(context)
+        val headsUpRonView = View(context)
+
         val mockRonModel1 = mock<TimerContentModel>()
         val mockRonModel2 = mock<TimerContentModel>()
-        val mockBinder1 = mock<DeferredContentViewBinder>()
-        doReturn(handle1).whenever(mockBinder1).setupContentViewBinder()
 
-        row.privateLayout.mContractedBinderHandle = handle0
+        val mockContractedViewBinder = mock<DeferredContentViewBinder>()
+        val mockExpandedViewBinder = mock<DeferredContentViewBinder>()
+        val mockHeadsUpViewBinder = mock<DeferredContentViewBinder>()
+
+        doReturn(contractedBinderHandle).whenever(mockContractedViewBinder).setupContentViewBinder()
+        doReturn(expandedBinderHandle).whenever(mockExpandedViewBinder).setupContentViewBinder()
+        doReturn(headsUpBinderHandle).whenever(mockHeadsUpViewBinder).setupContentViewBinder()
+
+        row.privateLayout.mContractedBinderHandle = oldContractedBinderHandle
+        row.privateLayout.mExpandedBinderHandle = oldExpandedBinderHandle
+        row.privateLayout.mHeadsUpBinderHandle = oldHeadsUpBinderHandle
         val entry = spy(row.entry)
         row.entry = entry
         val privateLayout = spy(row.privateLayout)
@@ -460,31 +811,87 @@
 
         // WHEN inflater inflates both a model and a view
         fakeRonContentModel = mockRonModel1
-        fakeRonViewHolder = InflatedContentViewHolder(view = ronView, binder = mockBinder1)
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+        fakeContractedRonViewHolder =
+            InflatedContentViewHolder(view = contractedRonView, binder = mockContractedViewBinder)
+        fakeExpandedRonViewHolder =
+            InflatedContentViewHolder(view = expandedRonView, binder = mockExpandedViewBinder)
+        fakeHeadsUpRonViewHolder =
+            InflatedContentViewHolder(view = headsUpRonView, binder = mockHeadsUpViewBinder)
+
+        val contentToInflate =
+            FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
+        inflateAndWait(notificationInflater, contentToInflate, row)
 
         // Validate that these 4 steps happen in this precise order
-        inOrder(handle0, entry, privateLayout, mockBinder1, handle1) {
-            verify(handle0).dispose()
+        inOrder(
+            oldContractedBinderHandle,
+            oldExpandedBinderHandle,
+            oldHeadsUpBinderHandle,
+            entry,
+            privateLayout,
+            mockContractedViewBinder,
+            mockExpandedViewBinder,
+            mockHeadsUpViewBinder,
+            contractedBinderHandle,
+            expandedBinderHandle,
+            headsUpBinderHandle
+        ) {
+            verify(oldContractedBinderHandle).dispose()
+            verify(oldExpandedBinderHandle).dispose()
+            verify(oldHeadsUpBinderHandle).dispose()
+
             verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel1 })
-            verify(privateLayout).setContractedChild(eq(ronView))
-            verify(mockBinder1).setupContentViewBinder()
-            verify(handle1, never()).dispose()
+
+            verify(privateLayout).setContractedChild(eq(contractedRonView))
+            verify(mockContractedViewBinder).setupContentViewBinder()
+
+            verify(privateLayout).setExpandedChild(eq(expandedRonView))
+            verify(mockExpandedViewBinder).setupContentViewBinder()
+
+            verify(privateLayout).setHeadsUpChild(eq(headsUpRonView))
+            verify(mockHeadsUpViewBinder).setupContentViewBinder()
+
+            verify(contractedBinderHandle, never()).dispose()
+            verify(expandedBinderHandle, never()).dispose()
+            verify(headsUpBinderHandle, never()).dispose()
         }
 
-        clearInvocations(handle0, entry, privateLayout, mockBinder1, handle1)
+        clearInvocations(
+            oldContractedBinderHandle,
+            oldExpandedBinderHandle,
+            oldHeadsUpBinderHandle,
+            entry,
+            privateLayout,
+            mockContractedViewBinder,
+            mockExpandedViewBinder,
+            mockHeadsUpViewBinder,
+            contractedBinderHandle,
+            expandedBinderHandle,
+            headsUpBinderHandle
+        )
 
         // THEN when the inflater inflates just a model
         fakeRonContentModel = mockRonModel2
-        fakeRonViewHolder = null
-        inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, row)
+        fakeContractedRonViewHolder = KeepExistingView
+        fakeExpandedRonViewHolder = KeepExistingView
+        fakeHeadsUpRonViewHolder = KeepExistingView
+
+        inflateAndWait(notificationInflater, contentToInflate, row)
 
         // Validate that for reinflation, the only thing we do us update the model
-        verify(handle1, never()).dispose()
+        verify(contractedBinderHandle, never()).dispose()
+        verify(expandedBinderHandle, never()).dispose()
+        verify(headsUpBinderHandle, never()).dispose()
         verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel2 })
         verify(privateLayout, never()).setContractedChild(any())
-        verify(mockBinder1, never()).setupContentViewBinder()
-        verify(handle1, never()).dispose()
+        verify(privateLayout, never()).setExpandedChild(any())
+        verify(privateLayout, never()).setHeadsUpChild(any())
+        verify(mockContractedViewBinder, never()).setupContentViewBinder()
+        verify(mockExpandedViewBinder, never()).setupContentViewBinder()
+        verify(mockHeadsUpViewBinder, never()).setupContentViewBinder()
+        verify(contractedBinderHandle, never()).dispose()
+        verify(expandedBinderHandle, never()).dispose()
+        verify(headsUpBinderHandle, never()).dispose()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index e4945fc..1a1af2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -342,7 +342,7 @@
         val stackTop = 200f
         val stackHeight = 800f
         whenever(ambientState.stackTop).thenReturn(stackTop)
-        whenever(ambientState.stackHeight).thenReturn(stackHeight)
+        whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
         val shelfTop = stackTop + stackHeight - shelf.height
         val stackScrollAlgorithmState = StackScrollAlgorithmState()
         val viewInShelf = mock(ExpandableView::class.java)
@@ -378,7 +378,7 @@
         val stackTop = 200f
         val stackHeight = 800f
         whenever(ambientState.stackTop).thenReturn(stackTop)
-        whenever(ambientState.stackHeight).thenReturn(stackHeight)
+        whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
         val paddingBetweenElements =
             context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
         whenever(ambientState.isShadeExpanded).thenReturn(true)
@@ -404,7 +404,7 @@
     fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
         // GIVEN
         whenever(ambientState.stackY).thenReturn(100f)
-        whenever(ambientState.stackHeight).thenReturn(100f)
+        whenever(ambientState.interpolatedStackHeight).thenReturn(100f)
         val paddingBetweenElements =
             context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
         val endOfStack = 200f + paddingBetweenElements
@@ -433,7 +433,7 @@
         val stackTop = 200f
         val stackHeight = 800f
         whenever(ambientState.stackTop).thenReturn(stackTop)
-        whenever(ambientState.stackHeight).thenReturn(stackHeight)
+        whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
         val paddingBetweenElements =
             context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
         whenever(ambientState.isShadeExpanded).thenReturn(true)
@@ -459,7 +459,7 @@
     fun updateState_withNullFirstViewInShelf_hideShelf() {
         // GIVEN
         whenever(ambientState.stackY).thenReturn(100f)
-        whenever(ambientState.stackHeight).thenReturn(100f)
+        whenever(ambientState.interpolatedStackHeight).thenReturn(100f)
         val paddingBetweenElements =
             context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
         val endOfStack = 200f + paddingBetweenElements
@@ -488,7 +488,7 @@
         val stackTop = 200f
         val stackHeight = 800f
         whenever(ambientState.stackTop).thenReturn(stackTop)
-        whenever(ambientState.stackHeight).thenReturn(stackHeight)
+        whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
         val paddingBetweenElements =
             context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
         val lastVisibleBackgroundChild = mock<ExpandableView>()
@@ -514,7 +514,7 @@
     fun updateState_withCollapsedShade_hideShelf() {
         // GIVEN
         whenever(ambientState.stackY).thenReturn(100f)
-        whenever(ambientState.stackHeight).thenReturn(100f)
+        whenever(ambientState.interpolatedStackHeight).thenReturn(100f)
         val paddingBetweenElements =
             context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
         val endOfStack = 200f + paddingBetweenElements
@@ -543,7 +543,7 @@
         val stackTop = 200f
         val stackHeight = 800f
         whenever(ambientState.stackTop).thenReturn(stackTop)
-        whenever(ambientState.stackHeight).thenReturn(stackHeight)
+        whenever(ambientState.interpolatedStackHeight).thenReturn(stackHeight)
         val paddingBetweenElements =
             context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
         whenever(ambientState.isShadeExpanded).thenReturn(true)
@@ -583,7 +583,7 @@
     fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
         // GIVEN
         whenever(ambientState.stackY).thenReturn(100f)
-        whenever(ambientState.stackHeight).thenReturn(100f)
+        whenever(ambientState.interpolatedStackHeight).thenReturn(100f)
         val paddingBetweenElements =
             context.resources.getDimensionPixelSize(R.dimen.notification_divider_height)
         val endOfStack = 200f + paddingBetweenElements
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..a06f4d2 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,8 @@
 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.notification.stack.shared.model.ShadeScrimBounds;
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -246,26 +247,12 @@
         when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat()))
                 .thenReturn((float) stackHeight);
 
-        mStackScroller.updateContentHeight();
+        mStackScroller.updateStackHeight();
 
         assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(stackHeight);
     }
 
     @Test
-    @DisableSceneContainer
-    public void testIntrinsicStackHeight_includesTopScrimPadding() {
-        int stackHeight = 300;
-        int topScrimPadding = px(R.dimen.notification_side_paddings);
-        when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat()))
-                .thenReturn((float) stackHeight);
-
-        mStackScroller.updateContentHeight();
-
-        assertThat(mStackScroller.getIntrinsicStackHeight())
-                .isEqualTo(stackHeight + topScrimPadding);
-    }
-
-    @Test
     @DisableSceneContainer // TODO(b/312473478): address disabled test
     public void testUpdateStackHeight_qsExpansionZero() {
         final float expansionFraction = 0.2f;
@@ -290,8 +277,8 @@
                 endHeight * StackScrollAlgorithm.START_FRACTION,
                 endHeight, expansionFraction);
 
-        mStackScroller.updateStackHeight(endHeight, expansionFraction);
-        assertThat(mAmbientState.getStackHeight()).isEqualTo(expected);
+        mStackScroller.updateInterpolatedStackHeight(endHeight, expansionFraction);
+        assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(expected);
     }
 
     @Test
@@ -310,7 +297,7 @@
 
         // THEN stackHeight and stackEndHeight are the same
         verify(mAmbientState).setStackEndHeight(stackEndHeight);
-        verify(mAmbientState).setStackHeight(stackEndHeight);
+        verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight);
     }
 
     @Test
@@ -330,7 +317,7 @@
 
         // THEN stackHeight is changed by the expansion frac
         verify(mAmbientState).setStackEndHeight(stackEndHeight);
-        verify(mAmbientState).setStackHeight(stackEndHeight * 0.75f);
+        verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight * 0.75f);
     }
 
     @Test
@@ -350,7 +337,7 @@
 
         // THEN stackHeight is measured from the stack top
         verify(mAmbientState).setStackEndHeight(stackEndHeight);
-        verify(mAmbientState).setStackHeight(stackEndHeight);
+        verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight);
     }
 
     @Test
@@ -364,7 +351,7 @@
         clearInvocations(mAmbientState);
         mStackScroller.updateStackEndHeightAndStackHeight(1f);
 
-        verify(mAmbientState).setStackHeight(eq(300f));
+        verify(mAmbientState).setInterpolatedStackHeight(eq(300f));
     }
 
     @Test
@@ -377,7 +364,7 @@
         clearInvocations(mAmbientState);
         mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction);
         verify(mAmbientState, never()).setStackEndHeight(anyFloat());
-        verify(mAmbientState).setStackHeight(anyFloat());
+        verify(mAmbientState).setInterpolatedStackHeight(anyFloat());
     }
 
     @Test
@@ -392,14 +379,14 @@
         clearInvocations(mAmbientState);
         mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction);
         verify(mAmbientState, never()).setStackEndHeight(anyFloat());
-        verify(mAmbientState).setStackHeight(anyFloat());
+        verify(mAmbientState).setInterpolatedStackHeight(anyFloat());
 
         // Validate that when the animation ends the stackEndHeight is recalculated immediately
         clearInvocations(mAmbientState);
         mStackScroller.setPanelFlinging(false);
         verify(mAmbientState).setFlinging(eq(false));
         verify(mAmbientState).setStackEndHeight(anyFloat());
-        verify(mAmbientState).setStackHeight(anyFloat());
+        verify(mAmbientState).setInterpolatedStackHeight(anyFloat());
     }
 
     @Test
@@ -441,6 +428,86 @@
     }
 
     @Test
+    @EnableSceneContainer
+    public void setExpandFraction_fullyCollapsed() {
+        // Given: NSSL has a height
+        when(mStackScroller.getHeight()).thenReturn(1200);
+        // And: stack bounds are set
+        float expandFraction = 0.0f;
+        float stackTop = 100;
+        float stackCutoff = 1100;
+        float stackHeight = stackCutoff - stackTop;
+        mStackScroller.setStackTop(stackTop);
+        mStackScroller.setStackCutoff(stackCutoff);
+
+        // When: panel is fully collapsed
+        mStackScroller.setExpandFraction(expandFraction);
+
+        // Then
+        assertThat(mAmbientState.getExpansionFraction()).isEqualTo(expandFraction);
+        assertThat(mAmbientState.isExpansionChanging()).isFalse();
+        assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight);
+        assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(
+                stackHeight * StackScrollAlgorithm.START_FRACTION);
+        assertThat(mAmbientState.isShadeExpanded()).isFalse();
+        assertThat(mStackScroller.getExpandedHeight()).isZero();
+    }
+
+    @Test
+    @EnableSceneContainer
+    public void setExpandFraction_expanding() {
+        // Given: NSSL has a height
+        when(mStackScroller.getHeight()).thenReturn(1200);
+        // And: stack bounds are set
+        float expandFraction = 0.6f;
+        float stackTop = 100;
+        float stackCutoff = 1100;
+        float stackHeight = stackCutoff - stackTop;
+        mStackScroller.setStackTop(stackTop);
+        mStackScroller.setStackCutoff(stackCutoff);
+
+        // When: panel is expanding
+        mStackScroller.setExpandFraction(expandFraction);
+
+        // Then
+        assertThat(mAmbientState.getExpansionFraction()).isEqualTo(expandFraction);
+        assertThat(mAmbientState.isExpansionChanging()).isTrue();
+        assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight);
+        assertThat(mAmbientState.getInterpolatedStackHeight()).isGreaterThan(
+                stackHeight * StackScrollAlgorithm.START_FRACTION);
+        assertThat(mAmbientState.getInterpolatedStackHeight()).isLessThan(stackHeight);
+        assertThat(mStackScroller.getExpandedHeight()).isGreaterThan(0f);
+        assertThat(mAmbientState.isShadeExpanded()).isTrue();
+    }
+
+    @Test
+    @EnableSceneContainer
+    public void setExpandFraction_fullyExpanded() {
+        // Given: NSSL has a height
+        int viewHeight = 1200;
+        when(mStackScroller.getHeight()).thenReturn(viewHeight);
+        // And: stack bounds are set
+        float expandFraction = 1.0f;
+        float stackTop = 100;
+        float stackCutoff = 1100;
+        float stackHeight = stackCutoff - stackTop;
+        mStackScroller.setStackTop(stackTop);
+        mStackScroller.setStackCutoff(stackCutoff);
+
+        // When: panel is fully expanded
+        mStackScroller.setExpandFraction(expandFraction);
+
+        // Then
+        assertThat(mAmbientState.getExpansionFraction()).isEqualTo(expandFraction);
+        assertThat(mAmbientState.isExpansionChanging()).isFalse();
+        assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight);
+        assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(stackHeight);
+        assertThat(mStackScroller.getExpandedHeight()).isEqualTo(viewHeight);
+        assertThat(mAmbientState.isShadeExpanded()).isTrue();
+    }
+
+    @Test
+    @DisableSceneContainer
     public void testSetExpandedHeight_listenerReceivedCallbacks() {
         final float expectedHeight = 0f;
 
@@ -467,6 +534,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testSetExpandedHeight_withSplitShade_doesntInterpolateStackHeight() {
         mTestableResources
                 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
@@ -827,7 +895,7 @@
 
     @Test
     @DisableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME})
-    @DisableSceneContainer // TODO(b/312473478): address lack of QS Header
+    @DisableSceneContainer
     public void testInsideQSHeader_noOffset() {
         ViewGroup qsHeader = mock(ViewGroup.class);
         Rect boundsOnScreen = new Rect(0, 0, 1000, 1000);
@@ -845,7 +913,7 @@
 
     @Test
     @DisableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME})
-    @DisableSceneContainer // TODO(b/312473478): address lack of QS Header
+    @DisableSceneContainer
     public void testInsideQSHeader_Offset() {
         ViewGroup qsHeader = mock(ViewGroup.class);
         Rect boundsOnScreen = new Rect(100, 100, 1000, 1000);
@@ -866,7 +934,7 @@
 
     @Test
     @EnableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME})
-    @DisableSceneContainer // TODO(b/312473478): address lack of QS Header
+    @DisableSceneContainer
     public void testInsideQSHeader_noOffset_qsCompose() {
         ViewGroup qsHeader = mock(ViewGroup.class);
         Rect boundsOnScreen = new Rect(0, 0, 1000, 1000);
@@ -893,7 +961,7 @@
 
     @Test
     @EnableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME})
-    @DisableSceneContainer // TODO(b/312473478): address lack of QS Header
+    @DisableSceneContainer
     public void testInsideQSHeader_Offset_qsCompose() {
         ViewGroup qsHeader = mock(ViewGroup.class);
         Rect boundsOnScreen = new Rect(100, 100, 1000, 1000);
@@ -922,6 +990,53 @@
     }
 
     @Test
+    @EnableSceneContainer
+    public void testIsInsideScrollableRegion_noScrim() {
+        mStackScroller.setLeftTopRightBottom(0, 0, 2000, 2000);
+
+        MotionEvent event = transformEventForView(createMotionEvent(250f, 250f), mStackScroller);
+        assertThat(mStackScroller.isInScrollableRegion(event)).isTrue();
+    }
+
+    @Test
+    @EnableSceneContainer
+    public void testIsInsideScrollableRegion_noOffset() {
+        mStackScroller.setLeftTopRightBottom(0, 0, 1000, 2000);
+        mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000));
+
+        MotionEvent event1 = transformEventForView(createMotionEvent(500f, 400f), mStackScroller);
+        assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse();
+
+        MotionEvent event2 = transformEventForView(createMotionEvent(50, 1000f), mStackScroller);
+        assertThat(mStackScroller.isInScrollableRegion(event2)).isFalse();
+
+        MotionEvent event3 = transformEventForView(createMotionEvent(950f, 1000f), mStackScroller);
+        assertThat(mStackScroller.isInScrollableRegion(event3)).isFalse();
+
+        MotionEvent event4 = transformEventForView(createMotionEvent(500f, 1000f), mStackScroller);
+        assertThat(mStackScroller.isInScrollableRegion(event4)).isTrue();
+    }
+
+    @Test
+    @EnableSceneContainer
+    public void testIsInsideScrollableRegion_offset() {
+        mStackScroller.setLeftTopRightBottom(1000, 0, 2000, 2000);
+        mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000));
+
+        MotionEvent event1 = transformEventForView(createMotionEvent(1500f, 400f), mStackScroller);
+        assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse();
+
+        MotionEvent event2 = transformEventForView(createMotionEvent(1050, 1000f), mStackScroller);
+        assertThat(mStackScroller.isInScrollableRegion(event2)).isFalse();
+
+        MotionEvent event3 = transformEventForView(createMotionEvent(1950f, 1000f), mStackScroller);
+        assertThat(mStackScroller.isInScrollableRegion(event3)).isFalse();
+
+        MotionEvent event4 = transformEventForView(createMotionEvent(1500f, 1000f), mStackScroller);
+        assertThat(mStackScroller.isInScrollableRegion(event4)).isTrue();
+    }
+
+    @Test
     @DisableSceneContainer // TODO(b/312473478): address disabled test
     public void setFractionToShade_recomputesStackHeight() {
         mStackScroller.setFractionToShade(1f);
@@ -1206,7 +1321,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 +1337,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 +1356,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 +1374,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 +1410,7 @@
     }
 
     @Test
-    @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+    @EnableSceneContainer
     public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() {
         // GIVEN NSSL is ready for HUN animations
         Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1372,7 +1487,7 @@
     private static MotionEvent transformEventForView(MotionEvent event, View view) {
         // From `ViewGroup#dispatchTransformedTouchEvent`
         MotionEvent transformed = event.copy();
-        transformed.offsetLocation(-view.getTop(), -view.getLeft());
+        transformed.offsetLocation(/* deltaX = */-view.getLeft(), /* deltaY = */ -view.getTop());
         return transformed;
     }
 
@@ -1408,4 +1523,9 @@
     }
 
     private abstract static class BooleanConsumer implements Consumer<Boolean> { }
+
+    private ShadeScrimShape createScrimShape(int left, int top, int right, int bottom) {
+        ShadeScrimBounds bounds = new ShadeScrimBounds(left, top, right, bottom);
+        return new ShadeScrimShape(bounds, 0, 0);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index ad029d7..3e8bf47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -296,6 +296,7 @@
             collapsedHeight = 100,
             intrinsicHeight = intrinsicHunHeight,
         )
+        ambientState.qsExpansionFraction = 1.0f
         whenever(notificationRow.isAboveShelf).thenReturn(true)
 
         // When
@@ -1113,6 +1114,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun shadeOpened_hunDoesNotOverlapQQS_hunShouldHaveNoShadow() {
         // Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height,
         // the height of HUN is equal to the height of QQS Panel,
@@ -1143,6 +1145,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun shadeClosed_hunShouldHaveFullShadow() {
         // Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding,
         // the height of HUN is equal to the height of QQS Panel,
@@ -1171,6 +1174,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun draggingHunToOpenShade_hunShouldHavePartialShadow() {
         // Given: shade is closed when HUN pops up,
         // now drags down the HUN to open shade
@@ -1446,7 +1450,7 @@
         // set stackEndHeight and stackHeight
         // ExpansionFractionWithoutShelf == stackHeight / stackEndHeight
         ambientState.stackEndHeight = 100f
-        ambientState.stackHeight = ambientState.stackEndHeight * fraction
+        ambientState.interpolatedStackHeight = ambientState.stackEndHeight * fraction
     }
 
     private fun resetViewStates_hunYTranslationIs(expected: Float) {
@@ -1530,7 +1534,7 @@
         // shade is fully open
         ambientState.expansionFraction = 1.0f
         with(fullStackHeight) {
-            ambientState.stackHeight = this
+            ambientState.interpolatedStackHeight = this
             ambientState.stackEndHeight = this
         }
         stackScrollAlgorithm.setIsExpanded(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
index 2f81027..c7919df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
@@ -20,6 +20,7 @@
 import android.os.UserManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -35,6 +36,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.never
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -46,12 +48,20 @@
 
     @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var userManager: UserManager
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        controller = ManagedProfileControllerImpl(context, mainExecutor, userTracker, userManager)
+        controller =
+            ManagedProfileControllerImpl(
+                context,
+                mainExecutor,
+                userTracker,
+                userManager,
+                keyguardUpdateMonitor
+            )
     }
 
     @Test
@@ -107,6 +117,24 @@
         captor.value.onProfilesChanged(userManager.getEnabledProfiles(1))
     }
 
+    @Test
+    fun hasWorkingProfile_setWorkModeEnabled_callsAwakenFromDream() {
+        `when`(userTracker.userId).thenReturn(1)
+        setupWorkingProfile(1)
+        `when`(userManager.requestQuietModeEnabled(any(), any())).thenReturn(false)
+        controller.hasActiveProfile()
+
+        controller.isWorkModeEnabled = true
+
+        verify(keyguardUpdateMonitor).awakenFromDream()
+    }
+
+    @Test
+    fun noWorkingProfile_setWorkModeEnabled_NoAwakenFromDreamCall() {
+        controller.isWorkModeEnabled = true
+        verify(keyguardUpdateMonitor, never()).awakenFromDream()
+    }
+
     private fun setupWorkingProfile(userId: Int) {
         `when`(userManager.getEnabledProfiles(userId))
             .thenReturn(
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..2ed3473 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,23 +17,32 @@
 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.internal.statusbar.StatusBarIcon
+import com.android.settingslib.notification.modes.TestModeBuilder
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.screenrecord.RecordingController
@@ -53,19 +62,18 @@
 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
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -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
@@ -131,8 +145,9 @@
     private lateinit var alarmCallbackCaptor:
         ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
 
-    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val testScope = kosmos.testScope
     private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider()
+    private val zenModeController = FakeZenModeController()
 
     private lateinit var executor: FakeExecutor
     private lateinit var statusBarPolicy: PhoneStatusBarPolicy
@@ -234,7 +249,7 @@
             statusBarPolicy.init()
             clearInvocations(iconController)
 
-            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+            fakeConnectedDisplayStateProvider.setState(State.CONNECTED)
             runCurrent()
 
             verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
@@ -246,7 +261,8 @@
             statusBarPolicy.init()
             clearInvocations(iconController)
 
-            fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
+            fakeConnectedDisplayStateProvider.setState(State.DISCONNECTED)
+            runCurrent()
 
             verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false)
         }
@@ -257,9 +273,12 @@
             statusBarPolicy.init()
             clearInvocations(iconController)
 
-            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
-            fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED)
-            fakeConnectedDisplayStateProvider.emit(State.CONNECTED)
+            fakeConnectedDisplayStateProvider.setState(State.CONNECTED)
+            runCurrent()
+            fakeConnectedDisplayStateProvider.setState(State.DISCONNECTED)
+            runCurrent()
+            fakeConnectedDisplayStateProvider.setState(State.CONNECTED)
+            runCurrent()
 
             inOrder(iconController).apply {
                 verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
@@ -274,7 +293,8 @@
             statusBarPolicy.init()
             clearInvocations(iconController)
 
-            fakeConnectedDisplayStateProvider.emit(State.CONNECTED_SECURE)
+            fakeConnectedDisplayStateProvider.setState(State.CONNECTED_SECURE)
+            runCurrent()
 
             verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true)
         }
@@ -374,6 +394,111 @@
         verify(iconController, never()).setIconVisibility(eq(SCREEN_RECORD_SLOT), any())
     }
 
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_UI, 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(mContext.packageName)
+                        .setIconResId(android.R.drawable.ic_lock_lock)
+                        .build(),
+                    TestModeBuilder()
+                        .setId("other")
+                        .setName("Other Mode")
+                        .setType(AutomaticZenRule.TYPE_OTHER)
+                        .setActive(true)
+                        .setPackage(SystemZenRules.PACKAGE_ANDROID)
+                        .setIconResId(android.R.drawable.ic_media_play)
+                        .build(),
+                )
+            )
+            runCurrent()
+
+            verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(true))
+            verify(iconController)
+                .setResourceIcon(
+                    eq(ZEN_SLOT),
+                    eq(mContext.packageName),
+                    eq(android.R.drawable.ic_lock_lock),
+                    any(), // non-null
+                    eq("Bedtime Mode"),
+                    eq(StatusBarIcon.Shape.FIXED_SPACE)
+                )
+
+            zenModeRepository.deactivateMode("bedtime")
+            runCurrent()
+
+            verify(iconController)
+                .setResourceIcon(
+                    eq(ZEN_SLOT),
+                    eq(null),
+                    eq(android.R.drawable.ic_media_play),
+                    any(), // non-null
+                    eq("Other Mode"),
+                    eq(StatusBarIcon.Shape.FIXED_SPACE)
+                )
+
+            zenModeRepository.deactivateMode("other")
+            runCurrent()
+
+            verify(iconController).setIconVisibility(eq(ZEN_SLOT), eq(false))
+        }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_UI, 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(), 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(), 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,14 +537,17 @@
             privacyItemController,
             privacyLogger,
             fakeConnectedDisplayStateProvider,
+            kosmos.zenModeInteractor,
             JavaAdapter(testScope.backgroundScope)
         )
     }
 
     private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor {
-        private val flow = MutableSharedFlow<State>()
+        private val flow = MutableStateFlow(State.DISCONNECTED)
 
-        suspend fun emit(value: State) = flow.emit(value)
+        fun setState(value: State) {
+            flow.value = value
+        }
 
         override val connectedDisplayState: Flow<State>
             get() = flow
@@ -433,4 +561,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/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index bea027f..6e337ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -432,8 +432,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -445,8 +444,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
@@ -460,8 +458,7 @@
         fragment.disable(DEFAULT_DISPLAY,
                 StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -474,8 +471,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -487,22 +483,19 @@
         when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
 
         // Ongoing call ended
         when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
 
         // Ongoing call started
         when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -536,8 +529,7 @@
                 /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         // THEN the old callback value is used, so the view is shown
-        assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
 
         // WHEN there's *no* ongoing call via old callback
         when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
@@ -548,8 +540,7 @@
                 /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         // THEN the old callback value is used, so the view is hidden
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -564,8 +555,7 @@
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
                 /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -576,8 +566,7 @@
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
                 /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
-        assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
         assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
@@ -592,8 +581,7 @@
         fragment.disable(DEFAULT_DISPLAY,
                 StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -607,8 +595,7 @@
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -620,15 +607,13 @@
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
                 /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
-        assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
 
         // Ongoing activity ended
         mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged(
                 /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -661,8 +646,7 @@
                 /* hasOngoingActivity= */ false, /* shouldAnimate= */ false);
 
         // THEN the new callback value is used, so the view is hidden
-        assertEquals(View.GONE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.GONE, getPrimaryOngoingActivityChipView().getVisibility());
 
         // WHEN there's *no* ongoing call via old callback
         when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
@@ -673,8 +657,7 @@
                 /* hasOngoingActivity= */ true, /* shouldAnimate= */ false);
 
         // THEN the new callback value is used, so the view is shown
-        assertEquals(View.VISIBLE,
-                mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
+        assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
     }
 
     @Test
@@ -1023,4 +1006,8 @@
     private View getNotificationAreaView() {
         return mFragment.getView().findViewById(R.id.notificationIcons);
     }
+
+    private View getPrimaryOngoingActivityChipView() {
+        return mFragment.getView().findViewById(R.id.ongoing_activity_chip_primary);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
new file mode 100644
index 0000000..90732d01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.phone.ui
+
+import android.app.Flags
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter
+import com.android.systemui.util.Assert
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class IconManagerTest : SysuiTestCase() {
+
+    private lateinit var underTest: IconManager
+    private lateinit var viewGroup: ViewGroup
+
+    @Before
+    fun setUp() {
+        Assert.setTestThread(Thread.currentThread())
+        viewGroup = LinearLayout(context)
+        underTest =
+            IconManager(
+                viewGroup,
+                StatusBarLocation.HOME,
+                mock<WifiUiAdapter>(defaultAnswer = RETURNS_DEEP_STUBS),
+                mock<MobileUiAdapter>(defaultAnswer = RETURNS_DEEP_STUBS),
+                mock<MobileContextProvider>(defaultAnswer = RETURNS_DEEP_STUBS),
+            )
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
+    fun addIcon_shapeWrapContent_addsIconViewWithVariableWidth() {
+        val sbIcon = newStatusBarIcon(StatusBarIcon.Shape.WRAP_CONTENT)
+
+        underTest.addIcon(0, "slot", false, sbIcon)
+
+        assertThat(viewGroup.childCount).isEqualTo(1)
+        val iconView = viewGroup.getChildAt(0) as StatusBarIconView
+        assertThat(iconView).isNotNull()
+
+        assertThat(iconView.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+        assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
+    fun addIcon_shapeFixedSpace_addsIconViewWithFixedWidth() {
+        val sbIcon = newStatusBarIcon(StatusBarIcon.Shape.FIXED_SPACE)
+
+        underTest.addIcon(0, "slot", false, sbIcon)
+
+        assertThat(viewGroup.childCount).isEqualTo(1)
+        val iconView = viewGroup.getChildAt(0) as StatusBarIconView
+        assertThat(iconView).isNotNull()
+
+        assertThat(iconView.layoutParams.width).isNotEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+        assertThat(iconView.layoutParams.width).isEqualTo(iconView.layoutParams.height)
+        assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.FIT_CENTER)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_UI_ICONS)
+    fun addIcon_iconsFlagOff_addsIconViewWithVariableWidth() {
+        val sbIcon = newStatusBarIcon(StatusBarIcon.Shape.FIXED_SPACE)
+
+        underTest.addIcon(0, "slot", false, sbIcon)
+
+        assertThat(viewGroup.childCount).isEqualTo(1)
+        val iconView = viewGroup.getChildAt(0) as StatusBarIconView
+        assertThat(iconView).isNotNull()
+
+        assertThat(iconView.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
+        assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER)
+    }
+
+    private fun newStatusBarIcon(shape: StatusBarIcon.Shape) =
+        StatusBarIcon(
+            UserHandle.CURRENT,
+            context.packageName,
+            Icon.createWithResource(context, android.R.drawable.ic_media_next),
+            0,
+            0,
+            "",
+            StatusBarIcon.Type.ResourceIcon,
+            shape,
+        )
+}
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..50a13b9 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,41 @@
             .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",
+            StatusBarIcon.Shape.FIXED_SPACE
+        )
+
+        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?.shape).isEqualTo(StatusBarIcon.Shape.FIXED_SPACE)
+        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/airplane/domain/interactor/AirplaneModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
index db3e533..7901f47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorTest.kt
@@ -19,13 +19,11 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
@@ -39,9 +37,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class AirplaneModeInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
-    private val mobileConnectionsRepository =
-        FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), mock<TableLogBuffer> {})
+    private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
     private val airplaneModeRepository = FakeAirplaneModeRepository()
     private val connectivityRepository = FakeConnectivityRepository()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
index b823333..8beed01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
@@ -19,12 +19,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -35,7 +36,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -43,10 +43,11 @@
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @RunWith(AndroidJUnit4::class)
 class AirplaneModeViewModelImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     private lateinit var underTest: AirplaneModeViewModelImpl
 
-    @Mock private lateinit var logger: TableLogBuffer
+    private val logger = logcatTableLogBuffer(kosmos, "AirplaneModeViewModelImplTest")
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
     private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var interactor: AirplaneModeInteractor
@@ -61,7 +62,7 @@
             AirplaneModeInteractor(
                 airplaneModeRepository,
                 connectivityRepository,
-                FakeMobileConnectionsRepository(),
+                kosmos.fakeMobileConnectionsRepository,
             )
         scope = CoroutineScope(IMMEDIATE)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 7d586cd..36f5236 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.tableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -42,11 +42,11 @@
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -56,7 +56,6 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -75,12 +74,13 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class MobileRepositorySwitcherTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private lateinit var underTest: MobileRepositorySwitcher
     private lateinit var realRepo: MobileConnectionsRepositoryImpl
     private lateinit var demoRepo: DemoMobileConnectionsRepository
     private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource
     private lateinit var wifiDataSource: DemoModeWifiDataSource
-    private lateinit var logFactory: TableLogBufferFactory
     private lateinit var wifiRepository: FakeWifiRepository
     private lateinit var connectivityRepository: ConnectivityRepository
 
@@ -95,16 +95,12 @@
     private val mobileMappings = FakeMobileMappingsProxy()
     private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
 
-    private val testDispatcher = UnconfinedTestDispatcher()
     private val scope = CoroutineScope(IMMEDIATE)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        logFactory =
-            TableLogBufferFactory(dumpManager, FakeSystemClock(), mock(), testDispatcher, scope)
-
         // Never start in demo mode
         whenever(demoModeController.isInDemoMode).thenReturn(false)
 
@@ -147,7 +143,7 @@
                 wifiDataSource = wifiDataSource,
                 scope = scope,
                 context = context,
-                logFactory = logFactory,
+                logFactory = kosmos.tableLogBufferFactory,
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index db6f5927..7d32021 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -23,16 +23,16 @@
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.tableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
@@ -43,12 +43,11 @@
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.After
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-import platform.test.runner.parameterized.Parameter
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 /**
  * Parameterized test for all of the common values of [FakeNetworkEventModel]. This test simply
@@ -60,19 +59,11 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
     SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
-    private val logFactory =
-        TableLogBufferFactory(
-            mock(),
-            FakeSystemClock(),
-            mock(),
-            testDispatcher,
-            testScope.backgroundScope,
-        )
-
     private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
     private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
 
@@ -99,7 +90,7 @@
                 wifiDataSource = mockWifiDataSource,
                 scope = testScope.backgroundScope,
                 context = context,
-                logFactory = logFactory,
+                logFactory = kosmos.tableLogBufferFactory,
             )
 
         connectionsRepo.startProcessingCommands()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 5e0d2fb0..5017dda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -24,8 +24,7 @@
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.TelephonyIcons.THREE_G
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.tableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -34,9 +33,9 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -56,21 +55,13 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
-    private val dumpManager: DumpManager = mock()
+    private val kosmos = testKosmos()
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
     private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
     private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
-    private val logFactory =
-        TableLogBufferFactory(
-            dumpManager,
-            FakeSystemClock(),
-            mock(),
-            testDispatcher,
-            testScope.backgroundScope,
-        )
 
     private lateinit var underTest: DemoMobileConnectionsRepository
     private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource
@@ -94,7 +85,7 @@
                 wifiDataSource = wifiDataSource,
                 scope = testScope.backgroundScope,
                 context = context,
-                logFactory = logFactory,
+                logFactory = kosmos.tableLogBufferFactory,
             )
 
         underTest.startProcessingCommands()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index fd4c370..c029850 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -29,8 +29,8 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.log.table.tableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
@@ -42,11 +42,11 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -73,23 +73,16 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class FullMobileConnectionRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private lateinit var underTest: FullMobileConnectionRepository
 
     private val flags =
         FakeFeatureFlagsClassic().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
 
-    private val systemClock = FakeSystemClock()
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
-    private val tableLogBuffer =
-        TableLogBuffer(
-            maxSize = 100,
-            name = "TestName",
-            systemClock,
-            mock(),
-            testDispatcher,
-            testScope.backgroundScope,
-        )
+    private val tableLogBuffer = logcatTableLogBuffer(kosmos, "TestName")
     private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
     private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
     private val connectivityManager = mock<ConnectivityManager>()
@@ -372,19 +365,10 @@
     @Test
     fun factory_reusesLogBuffersForSameConnection() =
         testScope.runTest {
-            val realLoggerFactory =
-                TableLogBufferFactory(
-                    mock(),
-                    FakeSystemClock(),
-                    mock(),
-                    testDispatcher,
-                    testScope.backgroundScope,
-                )
-
             val factory =
                 FullMobileConnectionRepository.Factory(
                     scope = testScope.backgroundScope,
-                    realLoggerFactory,
+                    kosmos.tableLogBufferFactory,
                     mobileFactory,
                     carrierMergedFactory,
                 )
@@ -416,19 +400,10 @@
     @Test
     fun factory_reusesLogBuffersForSameSubIDevenIfCarrierMerged() =
         testScope.runTest {
-            val realLoggerFactory =
-                TableLogBufferFactory(
-                    mock(),
-                    FakeSystemClock(),
-                    mock(),
-                    testDispatcher,
-                    testScope.backgroundScope,
-                )
-
             val factory =
                 FullMobileConnectionRepository.Factory(
                     scope = testScope.backgroundScope,
-                    realLoggerFactory,
+                    kosmos.tableLogBufferFactory,
                     mobileFactory,
                     carrierMergedFactory,
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 171520f..fe408e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -88,7 +88,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
@@ -121,7 +120,6 @@
 @RunWith(AndroidJUnit4::class)
 class MobileConnectionRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionRepositoryImpl
-    private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
     private val flags =
         FakeFeatureFlagsClassic().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
@@ -156,8 +154,6 @@
         MockitoAnnotations.initMocks(this)
         whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
 
-        connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
-
         underTest =
             MobileConnectionRepositoryImpl(
                 SUB_1_ID,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 2ab8c0a..0d82c79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -42,7 +42,6 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -97,7 +96,6 @@
 @SmallTest
 class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionRepositoryImpl
-    private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
     private val flags =
         FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
@@ -123,12 +121,6 @@
         MockitoAnnotations.initMocks(this)
         whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
 
-        connectionsRepo =
-            FakeMobileConnectionsRepository(
-                mobileMappings,
-                tableLogger,
-            )
-
         underTest =
             MobileConnectionRepositoryImpl(
                 SUB_1_ID,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 76982ae..4b6e313 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -28,7 +28,6 @@
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
-import android.os.Bundle
 import android.os.ParcelUuid
 import android.telephony.CarrierConfigManager
 import android.telephony.ServiceState
@@ -56,7 +55,6 @@
 import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -74,7 +72,6 @@
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.wifitrackerlib.MergedCarrierEntry
 import com.android.wifitrackerlib.WifiEntry
@@ -98,6 +95,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -108,11 +106,7 @@
 class MobileConnectionsRepositoryTest : SysuiTestCase() {
 
     private val flags =
-        FakeFeatureFlagsClassic().also {
-            it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
-            it.set(Flags.INSTANT_TETHER, true)
-            it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
-        }
+        FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
 
     private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
     private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
@@ -191,7 +185,6 @@
 
         wifiRepository =
             WifiRepositoryImpl(
-                flags,
                 testScope.backgroundScope,
                 mainExecutor,
                 testDispatcher,
@@ -602,47 +595,85 @@
 
     @SuppressLint("UnspecifiedRegisterReceiverFlag")
     @Test
-    fun testDeviceServiceStateFromBroadcast_eagerlyWatchesBroadcast() =
+    fun testDeviceEmergencyCallState_eagerlyChecksState() =
         testScope.runTest {
-            // Value starts out empty (null)
-            assertThat(underTest.deviceServiceState.value).isNull()
+            // Value starts out false
+            assertThat(underTest.isDeviceEmergencyCallCapable.value).isFalse()
+            whenever(telephonyManager.activeModemCount).thenReturn(1)
+            whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { _ ->
+                ServiceState().apply { isEmergencyOnly = true }
+            }
 
             // WHEN an appropriate intent gets sent out
-            val intent = serviceStateIntent(subId = -1, emergencyOnly = false)
+            val intent = serviceStateIntent(subId = -1)
             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                 context,
                 intent,
             )
             runCurrent()
 
-            // THEN the repo's state is updated
-            val expected = ServiceStateModel(isEmergencyOnly = false)
-            assertThat(underTest.deviceServiceState.value).isEqualTo(expected)
+            // THEN the repo's state is updated despite no listeners
+            assertThat(underTest.isDeviceEmergencyCallCapable.value).isEqualTo(true)
         }
 
     @Test
-    fun testDeviceServiceStateFromBroadcast_followsSubIdNegativeOne() =
+    fun testDeviceEmergencyCallState_aggregatesAcrossSlots_oneTrue() =
         testScope.runTest {
-            // device based state tracks -1
-            val intent = serviceStateIntent(subId = -1, emergencyOnly = false)
+            val latest by collectLastValue(underTest.isDeviceEmergencyCallCapable)
+
+            // GIVEN there are multiple slots
+            whenever(telephonyManager.activeModemCount).thenReturn(4)
+            // GIVEN only one of them reports ECM
+            whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
+                when (invocation.getArgument(0) as Int) {
+                    0 -> ServiceState().apply { isEmergencyOnly = false }
+                    1 -> ServiceState().apply { isEmergencyOnly = false }
+                    2 -> ServiceState().apply { isEmergencyOnly = true }
+                    3 -> ServiceState().apply { isEmergencyOnly = false }
+                    else -> null
+                }
+            }
+
+            // GIVEN a broadcast goes out for the appropriate subID
+            val intent = serviceStateIntent(subId = -1)
             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                 context,
                 intent,
             )
             runCurrent()
 
-            val deviceBasedState = ServiceStateModel(isEmergencyOnly = false)
-            assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState)
+            // THEN the device is in ECM, because one of the service states is
+            assertThat(latest).isTrue()
+        }
 
-            // ... and ignores any other subId
-            val intent2 = serviceStateIntent(subId = 1, emergencyOnly = true)
+    @Test
+    fun testDeviceEmergencyCallState_aggregatesAcrossSlots_allFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isDeviceEmergencyCallCapable)
+
+            // GIVEN there are multiple slots
+            whenever(telephonyManager.activeModemCount).thenReturn(4)
+            // GIVEN only one of them reports ECM
+            whenever(telephonyManager.getServiceStateForSlot(any())).thenAnswer { invocation ->
+                when (invocation.getArgument(0) as Int) {
+                    0 -> ServiceState().apply { isEmergencyOnly = false }
+                    1 -> ServiceState().apply { isEmergencyOnly = false }
+                    2 -> ServiceState().apply { isEmergencyOnly = false }
+                    3 -> ServiceState().apply { isEmergencyOnly = false }
+                    else -> null
+                }
+            }
+
+            // GIVEN a broadcast goes out for the appropriate subID
+            val intent = serviceStateIntent(subId = -1)
             fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
                 context,
-                intent2,
+                intent,
             )
             runCurrent()
 
-            assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState)
+            // THEN the device is in ECM, because one of the service states is
+            assertThat(latest).isFalse()
         }
 
     @Test
@@ -1549,15 +1580,8 @@
          */
         private fun serviceStateIntent(
             subId: Int,
-            emergencyOnly: Boolean = false,
         ): Intent {
-            val serviceState = ServiceState().apply { isEmergencyOnly = emergencyOnly }
-
-            val bundle = Bundle()
-            serviceState.fillInNotifierBundle(bundle)
-
             return Intent(Intent.ACTION_SERVICE_STATE).apply {
-                putExtras(bundle)
                 putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId)
             }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index e439aff..4fd830d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -18,7 +18,6 @@
 
 import android.platform.test.annotations.EnableFlags
 import android.telephony.CellSignalStrength
-import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -27,12 +26,12 @@
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
@@ -40,12 +39,12 @@
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.TestScope
@@ -61,21 +60,18 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class MobileIconInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private lateinit var underTest: MobileIconInteractor
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
     private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
 
-    private val subscriptionModel =
-        MutableStateFlow(
-            SubscriptionModel(
-                subscriptionId = SUB_1_ID,
-                carrierName = DEFAULT_NAME,
-                profileClass = PROFILE_CLASS_UNSET,
-            )
+    private val connectionRepository =
+        FakeMobileConnectionRepository(
+            SUB_1_ID,
+            logcatTableLogBuffer(kosmos, "MobileIconInteractorTest"),
         )
 
-    private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock())
-
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index cc0eae7..f6d439a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -28,8 +28,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -37,10 +36,10 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.UUID
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -59,6 +58,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class MobileIconsInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private lateinit var underTest: MobileIconsInteractor
     private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var connectionsRepository: FakeMobileConnectionsRepository
@@ -72,15 +73,7 @@
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
-    private val tableLogBuffer =
-        TableLogBuffer(
-            8,
-            "MobileIconsInteractorTest",
-            FakeSystemClock(),
-            mock(),
-            testDispatcher,
-            testScope.backgroundScope,
-        )
+    private val tableLogBuffer = logcatTableLogBuffer(kosmos, "MobileIconsInteractorTest")
 
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
 
@@ -897,13 +890,11 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
 
-            connectionsRepository.deviceServiceState.value =
-                ServiceStateModel(isEmergencyOnly = true)
+            connectionsRepository.isDeviceEmergencyCallCapable.value = true
 
             assertThat(latest).isTrue()
 
-            connectionsRepository.deviceServiceState.value =
-                ServiceStateModel(isEmergencyOnly = false)
+            connectionsRepository.isDeviceEmergencyCallCapable.value = false
 
             assertThat(latest).isFalse()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 42cb660..84846a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -28,12 +28,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
@@ -41,6 +41,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,13 +59,13 @@
 @RunWithLooper(setAsMainLooper = true)
 @OptIn(ExperimentalCoroutinesApi::class)
 class ModernStatusBarMobileViewTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     private lateinit var testableLooper: TestableLooper
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
-    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var viewLogger: MobileViewLogger
     @Mock private lateinit var constants: ConnectivityConstants
     private lateinit var interactor: FakeMobileIconInteractor
@@ -88,10 +89,11 @@
             AirplaneModeInteractor(
                 airplaneModeRepository,
                 FakeConnectivityRepository(),
-                FakeMobileConnectionsRepository(),
+                kosmos.fakeMobileConnectionsRepository,
             )
 
-        interactor = FakeMobileIconInteractor(tableLogBuffer)
+        interactor =
+            FakeMobileIconInteractor(logcatTableLogBuffer(kosmos, "ModernStatusBarMobileViewTest"))
         createViewModel()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index deb9fcf..f99fcac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -21,24 +21,23 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -59,13 +58,15 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private lateinit var commonImpl: MobileIconViewModelCommon
     private lateinit var homeIcon: HomeMobileIconViewModel
     private lateinit var qsIcon: QsMobileIconViewModel
     private lateinit var keyguardIcon: KeyguardMobileIconViewModel
     private lateinit var iconsInteractor: MobileIconsInteractor
     private lateinit var interactor: MobileIconInteractor
-    private lateinit var connectionsRepository: FakeMobileConnectionsRepository
+    private val connectionsRepository = kosmos.fakeMobileConnectionsRepository
     private lateinit var repository: FakeMobileConnectionRepository
     private lateinit var airplaneModeInteractor: AirplaneModeInteractor
 
@@ -76,9 +77,9 @@
             it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
         }
 
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var constants: ConnectivityConstants
-    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+    private val tableLogBuffer =
+        logcatTableLogBuffer(kosmos, "LocationBasedMobileIconViewModelTest")
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
 
     private val testDispatcher = UnconfinedTestDispatcher()
@@ -91,10 +92,8 @@
             AirplaneModeInteractor(
                 FakeAirplaneModeRepository(),
                 FakeConnectivityRepository(),
-                FakeMobileConnectionsRepository(),
+                connectionsRepository,
             )
-        connectionsRepository =
-            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
         repository =
             FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer).apply {
                 isInService.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index e510924..4c7cdfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -34,7 +34,7 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
@@ -51,6 +52,7 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -74,6 +76,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class MobileIconViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private var connectivityRepository = FakeConnectivityRepository()
 
     private lateinit var underTest: MobileIconViewModel
@@ -84,7 +88,7 @@
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
     private lateinit var airplaneModeInteractor: AirplaneModeInteractor
     @Mock private lateinit var constants: ConnectivityConstants
-    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+    private val tableLogBuffer = logcatTableLogBuffer(kosmos, "MobileIconViewModelTest")
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
 
     private val flags =
@@ -118,7 +122,7 @@
             AirplaneModeInteractor(
                 airplaneModeRepository,
                 connectivityRepository,
-                FakeMobileConnectionsRepository(),
+                kosmos.fakeMobileConnectionsRepository,
             )
 
         iconsInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index 47899a6..31ba837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -24,11 +24,10 @@
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
@@ -36,6 +35,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertFalse
@@ -58,12 +58,13 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class MobileIconsViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private lateinit var underTest: MobileIconsViewModel
     private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
 
     private lateinit var airplaneModeInteractor: AirplaneModeInteractor
-    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var constants: ConnectivityConstants
     @Mock private lateinit var logger: MobileViewLogger
     @Mock private lateinit var verboseLogger: VerboseMobileViewLogger
@@ -79,7 +80,7 @@
             AirplaneModeInteractor(
                 FakeAirplaneModeRepository(),
                 FakeConnectivityRepository(),
-                FakeMobileConnectionsRepository(),
+                kosmos.fakeMobileConnectionsRepository,
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index c3cc33f..bf31f1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -45,6 +45,7 @@
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.mock
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
@@ -88,7 +89,7 @@
     }
 
     @Test
-    fun icon_nullWhenShouldNotShow_satelliteNotAllowed() =
+    fun icon_null_satelliteNotAllowed() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
 
@@ -108,7 +109,30 @@
         }
 
     @Test
-    fun icon_nullWhenShouldNotShow_notAllOos() =
+    fun icon_null_connectedAndNotAllowed() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is not allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = false
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN satellite state is Connected. (this should not ever occur, but still)
+            repo.connectionState.value = SatelliteConnectionState.Connected
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // THEN icon is null despite the connected state
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun icon_null_notAllOos() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
 
@@ -127,9 +151,28 @@
             assertThat(latest).isNull()
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
-    fun icon_nullWhenShouldNotShow_isEmergencyOnly() =
+    fun icon_null_allOosAndNotAllowed() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = false
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // THEN icon is null because it is not allowed
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun icon_null_isEmergencyOnly() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
 
@@ -158,7 +201,7 @@
         }
 
     @Test
-    fun icon_nullWhenShouldNotShow_apmIsEnabled() =
+    fun icon_null_apmIsEnabled() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
 
@@ -177,9 +220,8 @@
             assertThat(latest).isNull()
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
-    fun icon_satelliteIsOn() =
+    fun icon_notNull_satelliteAllowedAndAllOos() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
 
@@ -201,7 +243,6 @@
             assertThat(latest).isInstanceOf(Icon::class.java)
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun icon_hysteresisWhenEnablingIcon() =
         testScope.runTest {
@@ -234,9 +275,56 @@
             assertThat(latest).isNull()
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
-    fun icon_deviceIsProvisioned() =
+    fun icon_ignoresHysteresis_whenConnected() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = true
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // GIVEN satellite reports that it is Connected
+            repo.connectionState.value = SatelliteConnectionState.Connected
+
+            // THEN icon is non null because we are connected, despite the normal OOS icon waiting
+            // 10 seconds for hysteresis
+            assertThat(latest).isInstanceOf(Icon::class.java)
+        }
+
+    @Test
+    fun icon_ignoresHysteresis_whenOn() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN satellite is allowed
+            repo.isSatelliteAllowedForCurrentLocation.value = true
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // GIVEN satellite reports that it is Connected
+            repo.connectionState.value = SatelliteConnectionState.On
+
+            // THEN icon is non null because the connection state is On, despite the normal OOS icon
+            // waiting 10 seconds for hysteresis
+            assertThat(latest).isInstanceOf(Icon::class.java)
+        }
+
+    @Test
+    fun icon_satelliteIsProvisioned() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
 
@@ -267,7 +355,6 @@
             assertThat(latest).isInstanceOf(Icon::class.java)
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun icon_wifiIsActive() =
         testScope.runTest {
@@ -324,7 +411,28 @@
         }
 
     @Test
-    fun carrierText_nullWhenShouldNotShow_notAllOos() =
+    fun carrierText_null_notAllOos() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.carrierText)
+
+            // GIVEN satellite is allowed + off
+            repo.isSatelliteAllowedForCurrentLocation.value = true
+            repo.connectionState.value = SatelliteConnectionState.Off
+
+            // GIVEN all icons are not OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = true
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // THEN carrier text is null because we have service
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun carrierText_notNull_notAllOos_butConnected() =
         testScope.runTest {
             val latest by collectLastValue(underTest.carrierText)
 
@@ -340,39 +448,9 @@
             // GIVEN apm is disabled
             airplaneModeRepository.setIsAirplaneMode(false)
 
-            // THEN carrier text is null because we have service
-            assertThat(latest).isNull()
-        }
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    @Test
-    fun carrierText_nullWhenShouldNotShow_isEmergencyOnly() =
-        testScope.runTest {
-            val latest by collectLastValue(underTest.carrierText)
-
-            // GIVEN satellite is allowed + connected
-            repo.isSatelliteAllowedForCurrentLocation.value = true
-            repo.connectionState.value = SatelliteConnectionState.Connected
-
-            // GIVEN all icons are OOS
-            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
-            i1.isInService.value = false
-            i1.isEmergencyOnly.value = false
-
-            // GIVEN apm is disabled
-            airplaneModeRepository.setIsAirplaneMode(false)
-
-            // Wait for delay to be completed
-            advanceTimeBy(10.seconds)
-
-            // THEN carrier text is set because we don't have service
+            // THEN carrier text is not null, because it is connected
+            // This case should never happen, but let's test it anyway
             assertThat(latest).isNotNull()
-
-            // GIVEN the connection is emergency only
-            i1.isEmergencyOnly.value = true
-
-            // THEN carrier text is null because we have emergency connection
-            assertThat(latest).isNull()
         }
 
     @Test
@@ -396,7 +474,6 @@
             assertThat(latest).isNull()
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun carrierText_satelliteIsOn() =
         testScope.runTest {
@@ -421,9 +498,8 @@
             assertThat(latest).isNotNull()
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
-    fun carrierText_hysteresisWhenEnablingText() =
+    fun carrierText_noHysteresisWhenEnablingText_connected() =
         testScope.runTest {
             val latest by collectLastValue(underTest.carrierText)
 
@@ -439,23 +515,10 @@
             // GIVEN apm is disabled
             airplaneModeRepository.setIsAirplaneMode(false)
 
-            // THEN carrier text is null because of the hysteresis
-            assertThat(latest).isNull()
-
-            // Wait for delay to be completed
-            advanceTimeBy(10.seconds)
-
-            // THEN carrier text is set after the delay
+            // THEN carrier text is not null because we skip hysteresis when connected
             assertThat(latest).isNotNull()
-
-            // GIVEN apm is enabled
-            airplaneModeRepository.setIsAirplaneMode(true)
-
-            // THEN carrier text is null immediately
-            assertThat(latest).isNull()
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun carrierText_deviceIsProvisioned() =
         testScope.runTest {
@@ -489,7 +552,6 @@
             assertThat(latest).isNotNull()
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun carrierText_wifiIsActive() =
         testScope.runTest {
@@ -526,9 +588,8 @@
             assertThat(latest).isNotNull()
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
-    fun carrierText_connectionStateUnknown_null() =
+    fun carrierText_connectionStateUnknown_usesEmergencyOnlyText() =
         testScope.runTest {
             val latest by collectLastValue(underTest.carrierText)
 
@@ -544,12 +605,12 @@
             // Wait for delay to be completed
             advanceTimeBy(10.seconds)
 
-            assertThat(latest).isNull()
+            assertThat(latest)
+                .isEqualTo(context.getString(R.string.satellite_emergency_only_carrier_text))
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
-    fun carrierText_connectionStateOff_null() =
+    fun carrierText_connectionStateOff_usesEmergencyOnlyText() =
         testScope.runTest {
             val latest by collectLastValue(underTest.carrierText)
 
@@ -565,10 +626,10 @@
             // Wait for delay to be completed
             advanceTimeBy(10.seconds)
 
-            assertThat(latest).isNull()
+            assertThat(latest)
+                .isEqualTo(context.getString(R.string.satellite_emergency_only_carrier_text))
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun carrierText_connectionStateOn_notConnectedString() =
         testScope.runTest {
@@ -590,7 +651,6 @@
                 .isEqualTo(context.getString(R.string.satellite_connected_carrier_text))
         }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun carrierText_connectionStateConnected_connectedString() =
         testScope.runTest {
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/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 60750cf..7ae6ea5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -415,9 +415,9 @@
         }
 
     @Test
-    fun ongoingActivityChip_matchesViewModel() =
+    fun primaryOngoingActivityChip_matchesViewModel() =
         testScope.runTest {
-            val latest by collectLastValue(underTest.ongoingActivityChip)
+            val latest by collectLastValue(underTest.primaryOngoingActivityChip)
 
             kosmos.screenRecordRepository.screenRecordState.value = ScreenRecordModel.Recording
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
index cefdf7e..e71f521 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
@@ -28,7 +28,7 @@
 
     override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>()
 
-    override val ongoingActivityChip: MutableStateFlow<OngoingActivityChipModel> =
+    override val primaryOngoingActivityChip: MutableStateFlow<OngoingActivityChipModel> =
         MutableStateFlow(OngoingActivityChipModel.Hidden())
 
     override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 2238bff..50f262c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.connectivity.WifiIcons
@@ -49,6 +49,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
@@ -61,6 +62,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class InternetTileViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private lateinit var underTest: InternetTileViewModel
     private lateinit var mobileIconsInteractor: MobileIconsInteractor
 
@@ -73,7 +76,7 @@
     private val wifiInteractor =
         WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
 
-    private val tableLogBuffer: TableLogBuffer = mock()
+    private val tableLogBuffer = logcatTableLogBuffer(kosmos, "InternetTileViewModelTest")
     private val carrierConfigTracker: CarrierConfigTracker = mock()
 
     private val mobileConnectionsRepository =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index f8d50f5..b9c57d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -25,8 +25,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
@@ -76,7 +74,6 @@
     // inside each test case without needing to manually recreate the repository.
     private val underTest: WifiRepositoryImpl by lazy {
         WifiRepositoryImpl(
-            featureFlags,
             testScope.backgroundScope,
             executor,
             dispatcher,
@@ -89,7 +86,6 @@
 
     private val executor = FakeExecutor(FakeSystemClock())
     private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock())
-    private val featureFlags = FakeFeatureFlags()
     private val tableLogger = mock<TableLogBuffer>()
     private val wifiManager =
         mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) }
@@ -103,8 +99,6 @@
 
     @Before
     fun setUp() {
-        featureFlags.set(Flags.INSTANT_TETHER, false)
-        featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
         whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor), any()))
             .thenReturn(wifiPickerTracker)
     }
@@ -394,7 +388,6 @@
     @Test
     fun wifiNetwork_notHotspot_none() =
         testScope.runTest {
-            featureFlags.set(Flags.INSTANT_TETHER, true)
             val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry =
@@ -409,7 +402,6 @@
     @Test
     fun wifiNetwork_hotspot_unknown() =
         testScope.runTest {
-            featureFlags.set(Flags.INSTANT_TETHER, true)
             val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN)
@@ -423,7 +415,6 @@
     @Test
     fun wifiNetwork_hotspot_phone() =
         testScope.runTest {
-            featureFlags.set(Flags.INSTANT_TETHER, true)
             val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_PHONE)
@@ -437,7 +428,6 @@
     @Test
     fun wifiNetwork_hotspot_tablet() =
         testScope.runTest {
-            featureFlags.set(Flags.INSTANT_TETHER, true)
             val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_TABLET)
@@ -451,7 +441,6 @@
     @Test
     fun wifiNetwork_hotspot_laptop() =
         testScope.runTest {
-            featureFlags.set(Flags.INSTANT_TETHER, true)
             val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_LAPTOP)
@@ -465,7 +454,6 @@
     @Test
     fun wifiNetwork_hotspot_watch() =
         testScope.runTest {
-            featureFlags.set(Flags.INSTANT_TETHER, true)
             val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH)
@@ -479,7 +467,6 @@
     @Test
     fun wifiNetwork_hotspot_auto() =
         testScope.runTest {
-            featureFlags.set(Flags.INSTANT_TETHER, true)
             val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_AUTO)
@@ -493,7 +480,6 @@
     @Test
     fun wifiNetwork_hotspot_invalid() =
         testScope.runTest {
-            featureFlags.set(Flags.INSTANT_TETHER, true)
             val latest by collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry = createHotspotWithType(1234)
@@ -505,23 +491,6 @@
         }
 
     @Test
-    fun wifiNetwork_hotspot_flagOff_valueNotUsed() =
-        testScope.runTest {
-            // WHEN the flag is off
-            featureFlags.set(Flags.INSTANT_TETHER, false)
-
-            val latest by collectLastValue(underTest.wifiNetwork)
-
-            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH)
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
-            getCallback().onWifiEntriesChanged()
-
-            // THEN NONE is always used, even if the wifi entry does have a hotspot device type
-            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
-                .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
-        }
-
-    @Test
     fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
@@ -826,7 +795,6 @@
     @Test
     fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf())
@@ -839,7 +807,6 @@
     @Test
     fun secondaryNetworks_oneActiveEntry_hasOne() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             val wifiEntry = mock<WifiEntry>()
@@ -853,7 +820,6 @@
     @Test
     fun secondaryNetworks_multipleActiveEntries_hasMultiple() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             val wifiEntry1 = mock<WifiEntry>()
@@ -868,7 +834,6 @@
     @Test
     fun secondaryNetworks_mapsToInactive() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             val inactiveEntry =
@@ -884,7 +849,6 @@
     @Test
     fun secondaryNetworks_mapsToActive() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
@@ -900,7 +864,6 @@
     @Test
     fun secondaryNetworks_mapsToCarrierMerged() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             val carrierMergedEntry =
@@ -917,7 +880,6 @@
     @Test
     fun secondaryNetworks_mapsMultipleInOrder() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
@@ -937,7 +899,6 @@
     @Test
     fun secondaryNetworks_filtersOutConnectedEntry() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             val connectedEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(1) }
@@ -959,7 +920,6 @@
     @Test
     fun secondaryNetworks_noConnectedEntry_hasAllActiveEntries() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
@@ -978,7 +938,6 @@
     @Test
     fun secondaryNetworks_filtersOutPrimaryNetwork() =
         testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
             val latest by collectLastValue(underTest.secondaryNetworks)
 
             val primaryEntry =
@@ -1001,20 +960,6 @@
         }
 
     @Test
-    fun secondaryNetworks_flagOff_noNetworks() =
-        testScope.runTest {
-            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
-            val latest by collectLastValue(underTest.secondaryNetworks)
-
-            val wifiEntry = mock<WifiEntry>()
-            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry))
-
-            getCallback().onWifiEntriesChanged()
-
-            assertThat(latest).isEmpty()
-        }
-
-    @Test
     fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
         testScope.runTest {
             collectLastValue(underTest.wifiNetwork)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 80b10c0..161c4f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -26,7 +26,7 @@
 import android.widget.ImageView
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel.Companion.viewModelForLocation
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -61,10 +62,11 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
 class ModernStatusBarWifiViewTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     private lateinit var testableLooper: TestableLooper
 
-    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+    private val tableLogBuffer = logcatTableLogBuffer(kosmos, "ModernStatusBarWifiViewTest")
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -91,7 +93,7 @@
                 AirplaneModeInteractor(
                     airplaneModeRepository,
                     connectivityRepository,
-                    FakeMobileConnectionsRepository(),
+                    kosmos.fakeMobileConnectionsRepository,
                 ),
                 tableLogBuffer,
                 scope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 489319e..d2a4bf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon.Companion.NO_INTERNET
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -64,6 +65,7 @@
 @RunWith(Parameterized::class)
 internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase) :
     SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     private lateinit var underTest: WifiViewModel
 
@@ -91,7 +93,7 @@
                 AirplaneModeInteractor(
                     airplaneModeRepository,
                     connectivityRepository,
-                    FakeMobileConnectionsRepository(),
+                    kosmos.fakeMobileConnectionsRepository,
                 ),
                 tableLogBuffer,
                 scope,
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/CreateUserActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
index 84cd79d..25ceea9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
@@ -26,8 +26,8 @@
                 val dialog: Dialog = mock()
                 whenever(
                         createDialog(
-                            /* activity = */ nullable(),
-                            /* activityStarter = */ nullable(),
+                            /* activity = */ any(),
+                            /* activityStarter = */ any(),
                             /* isMultipleAdminsEnabled = */ any(),
                             /* successCallback = */ nullable(),
                             /* cancelCallback = */ nullable()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 37a73cf..c235954 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -24,21 +24,21 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.kosmos.unconfinedTestScope
 import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectedUserModel
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.settings.unconfinedDispatcherFakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -52,143 +52,153 @@
 @RunWith(AndroidJUnit4::class)
 class UserRepositoryImplTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+    private val testDispatcher = kosmos.unconfinedTestDispatcher
+    private val testScope = kosmos.unconfinedTestScope
+    private val globalSettings = kosmos.unconfinedDispatcherFakeGlobalSettings
+
     @Mock private lateinit var manager: UserManager
 
     private lateinit var underTest: UserRepositoryImpl
 
-    private lateinit var globalSettings: FakeGlobalSettings
     private lateinit var tracker: FakeUserTracker
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        globalSettings = FakeGlobalSettings()
         tracker = FakeUserTracker()
     }
 
     @Test
-    fun userSwitcherSettings() = runSelfCancelingTest {
-        setUpGlobalSettings(
-            isSimpleUserSwitcher = true,
-            isAddUsersFromLockscreen = true,
-            isUserSwitcherEnabled = true,
-        )
-        underTest = create(this)
+    fun userSwitcherSettings() =
+        testScope.runTest {
+            setUpGlobalSettings(
+                isSimpleUserSwitcher = true,
+                isAddUsersFromLockscreen = true,
+                isUserSwitcherEnabled = true,
+            )
+            underTest = create(testScope.backgroundScope)
+            var value: UserSwitcherSettingsModel? = null
+            val job = underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
 
-        var value: UserSwitcherSettingsModel? = null
-        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+            assertUserSwitcherSettings(
+                model = value,
+                expectedSimpleUserSwitcher = true,
+                expectedAddUsersFromLockscreen = true,
+                expectedUserSwitcherEnabled = true,
+            )
 
-        assertUserSwitcherSettings(
-            model = value,
-            expectedSimpleUserSwitcher = true,
-            expectedAddUsersFromLockscreen = true,
-            expectedUserSwitcherEnabled = true,
-        )
-
-        setUpGlobalSettings(
-            isSimpleUserSwitcher = false,
-            isAddUsersFromLockscreen = true,
-            isUserSwitcherEnabled = true,
-        )
-        assertUserSwitcherSettings(
-            model = value,
-            expectedSimpleUserSwitcher = false,
-            expectedAddUsersFromLockscreen = true,
-            expectedUserSwitcherEnabled = true,
-        )
-    }
+            setUpGlobalSettings(
+                isSimpleUserSwitcher = false,
+                isAddUsersFromLockscreen = true,
+                isUserSwitcherEnabled = true,
+            )
+            assertUserSwitcherSettings(
+                model = value,
+                expectedSimpleUserSwitcher = false,
+                expectedAddUsersFromLockscreen = true,
+                expectedUserSwitcherEnabled = true,
+            )
+            job.cancel()
+        }
 
     @Test
-    fun userSwitcherSettings_isUserSwitcherEnabled_notInitialized() = runSelfCancelingTest {
-        underTest = create(this)
+    fun userSwitcherSettings_isUserSwitcherEnabled_notInitialized() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
 
-        var value: UserSwitcherSettingsModel? = null
-        underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+            var value: UserSwitcherSettingsModel? = null
+            val job = underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
 
-        assertUserSwitcherSettings(
-            model = value,
-            expectedSimpleUserSwitcher = false,
-            expectedAddUsersFromLockscreen = false,
-            expectedUserSwitcherEnabled =
-                context.resources.getBoolean(
-                    com.android.internal.R.bool.config_showUserSwitcherByDefault
-                ),
-        )
-    }
+            assertUserSwitcherSettings(
+                model = value,
+                expectedSimpleUserSwitcher = false,
+                expectedAddUsersFromLockscreen = false,
+                expectedUserSwitcherEnabled =
+                    context.resources.getBoolean(
+                        com.android.internal.R.bool.config_showUserSwitcherByDefault
+                    ),
+            )
+            job.cancel()
+        }
 
     @Test
-    fun refreshUsers() = runSelfCancelingTest {
-        val mainUserId = 10
-        val mainUser = mock(UserHandle::class.java)
-        whenever(manager.mainUser).thenReturn(mainUser)
-        whenever(mainUser.identifier).thenReturn(mainUserId)
+    fun refreshUsers() =
+        testScope.runTest {
+            val mainUserId = 10
+            val mainUser = mock(UserHandle::class.java)
+            whenever(manager.mainUser).thenReturn(mainUser)
+            whenever(mainUser.identifier).thenReturn(mainUserId)
 
-        underTest = create(this)
-        val initialExpectedValue =
-            setUpUsers(
-                count = 3,
-                selectedIndex = 0,
-            )
-        var userInfos: List<UserInfo>? = null
-        var selectedUserInfo: UserInfo? = null
-        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+            underTest = create(testScope.backgroundScope)
+            val initialExpectedValue =
+                setUpUsers(
+                    count = 3,
+                    selectedIndex = 0,
+                )
+            var userInfos: List<UserInfo>? = null
+            var selectedUserInfo: UserInfo? = null
+            val job1 = underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+            val job2 = underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
 
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(initialExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+            underTest.refreshUsers()
+            assertThat(userInfos).isEqualTo(initialExpectedValue)
+            assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+            assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
 
-        val secondExpectedValue =
-            setUpUsers(
-                count = 4,
-                selectedIndex = 1,
-            )
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(secondExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+            val secondExpectedValue =
+                setUpUsers(
+                    count = 4,
+                    selectedIndex = 1,
+                )
+            underTest.refreshUsers()
+            assertThat(userInfos).isEqualTo(secondExpectedValue)
+            assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+            assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
 
-        val selectedNonGuestUserId = selectedUserInfo?.id
-        val thirdExpectedValue =
-            setUpUsers(
-                count = 2,
-                isLastGuestUser = true,
-                selectedIndex = 1,
-            )
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(thirdExpectedValue)
-        assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
-        assertThat(selectedUserInfo?.isGuest).isTrue()
-        assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
-        assertThat(underTest.mainUserId).isEqualTo(mainUserId)
-    }
+            val selectedNonGuestUserId = selectedUserInfo?.id
+            val thirdExpectedValue =
+                setUpUsers(
+                    count = 2,
+                    isLastGuestUser = true,
+                    selectedIndex = 1,
+                )
+            underTest.refreshUsers()
+            assertThat(userInfos).isEqualTo(thirdExpectedValue)
+            assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+            assertThat(selectedUserInfo?.isGuest).isTrue()
+            assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+            assertThat(underTest.mainUserId).isEqualTo(mainUserId)
+            job1.cancel()
+            job2.cancel()
+        }
 
     @Test
-    fun refreshUsers_sortsByCreationTime_guestUserLast() = runSelfCancelingTest {
-        underTest = create(this)
-        val unsortedUsers =
-            setUpUsers(
-                count = 3,
-                selectedIndex = 0,
-                isLastGuestUser = true,
-            )
-        unsortedUsers[0].creationTime = 999
-        unsortedUsers[1].creationTime = 900
-        unsortedUsers[2].creationTime = 950
-        val expectedUsers =
-            listOf(
-                unsortedUsers[1],
-                unsortedUsers[0],
-                unsortedUsers[2], // last because this is the guest
-            )
-        var userInfos: List<UserInfo>? = null
-        underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+    fun refreshUsers_sortsByCreationTime_guestUserLast() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
+            val unsortedUsers =
+                setUpUsers(
+                    count = 3,
+                    selectedIndex = 0,
+                    isLastGuestUser = true,
+                )
+            unsortedUsers[0].creationTime = 999
+            unsortedUsers[1].creationTime = 900
+            unsortedUsers[2].creationTime = 950
+            val expectedUsers =
+                listOf(
+                    unsortedUsers[1],
+                    unsortedUsers[0],
+                    unsortedUsers[2], // last because this is the guest
+                )
+            var userInfos: List<UserInfo>? = null
+            val job = underTest.userInfos.onEach { userInfos = it }.launchIn(this)
 
-        underTest.refreshUsers()
-        assertThat(userInfos).isEqualTo(expectedUsers)
-    }
+            underTest.refreshUsers()
+            assertThat(userInfos).isEqualTo(expectedUsers)
+            job.cancel()
+        }
 
     private fun setUpUsers(
         count: Int,
@@ -206,58 +216,68 @@
         tracker.set(userInfos, selectedIndex)
         return userInfos
     }
-    @Test
-    fun userTrackerCallback_updatesSelectedUserInfo() = runSelfCancelingTest {
-        underTest = create(this)
-        var selectedUserInfo: UserInfo? = null
-        underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
-        setUpUsers(
-            count = 2,
-            selectedIndex = 0,
-        )
-        tracker.onProfileChanged()
-        assertThat(selectedUserInfo?.id).isEqualTo(0)
-        setUpUsers(
-            count = 2,
-            selectedIndex = 1,
-        )
-        tracker.onProfileChanged()
-        assertThat(selectedUserInfo?.id).isEqualTo(1)
-    }
 
     @Test
-    fun userTrackerCallback_updatesSelectionStatus() = runSelfCancelingTest {
-        underTest = create(this)
-        var selectedUser: SelectedUserModel? = null
-        underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
-        setUpUsers(count = 2, selectedIndex = 1)
+    fun userTrackerCallback_updatesSelectedUserInfo() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
+            var selectedUserInfo: UserInfo? = null
+            val job = underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
 
-        // WHEN the user is changing
-        tracker.onUserChanging(userId = 1)
+            setUpUsers(
+                count = 2,
+                selectedIndex = 0,
+            )
+            tracker.onProfileChanged()
+            assertThat(selectedUserInfo?.id).isEqualTo(0)
+            setUpUsers(
+                count = 2,
+                selectedIndex = 1,
+            )
+            tracker.onProfileChanged()
+            assertThat(selectedUserInfo?.id).isEqualTo(1)
+            job.cancel()
+        }
 
-        // THEN the selection status is IN_PROGRESS
-        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+    @Test
+    fun userTrackerCallback_updatesSelectionStatus() =
+        testScope.runTest {
+            underTest = create(testScope.backgroundScope)
+            var selectedUser: SelectedUserModel? = null
+            val job = underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
 
-        // WHEN the user has finished changing
-        tracker.onUserChanged(userId = 1)
+            setUpUsers(count = 2, selectedIndex = 1)
 
-        // THEN the selection status is COMPLETE
-        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+            // WHEN the user is changing
+            tracker.onUserChanging(userId = 1)
 
-        tracker.onProfileChanged()
-        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
+            // THEN the selection status is IN_PROGRESS
+            assertThat(selectedUser!!.selectionStatus)
+                .isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
 
-        setUpUsers(count = 2, selectedIndex = 0)
+            // WHEN the user has finished changing
+            tracker.onUserChanged(userId = 1)
 
-        tracker.onUserChanging(userId = 0)
-        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+            // THEN the selection status is COMPLETE
+            assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
 
-        // WHEN a profile change occurs while a user is changing
-        tracker.onProfileChanged()
+            tracker.onProfileChanged()
+            assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE)
 
-        // THEN the selection status remains as IN_PROGRESS
-        assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
-    }
+            setUpUsers(count = 2, selectedIndex = 0)
+
+            tracker.onUserChanging(userId = 0)
+            assertThat(selectedUser!!.selectionStatus)
+                .isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+
+            // WHEN a profile change occurs while a user is changing
+            tracker.onProfileChanged()
+
+            // THEN the selection status remains as IN_PROGRESS
+            assertThat(selectedUser!!.selectionStatus)
+                .isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS)
+            job.cancel()
+        }
 
     private fun createUserInfo(
         id: Int,
@@ -308,26 +328,13 @@
         assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
     }
 
-    /**
-     * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
-     * is then automatically canceled and cleaned-up.
-     */
-    private fun runSelfCancelingTest(
-        block: suspend CoroutineScope.() -> Unit,
-    ) =
-        runBlocking(Dispatchers.Main.immediate) {
-            val scope = CoroutineScope(coroutineContext + Job())
-            block(scope)
-            scope.cancel()
-        }
-
-    private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+    private fun create(scope: CoroutineScope): UserRepositoryImpl {
         return UserRepositoryImpl(
             appContext = context,
             manager = manager,
             applicationScope = scope,
-            mainDispatcher = IMMEDIATE,
-            backgroundDispatcher = IMMEDIATE,
+            mainDispatcher = testDispatcher,
+            backgroundDispatcher = testDispatcher,
             globalSettings = globalSettings,
             tracker = tracker,
         )
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/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
index 88b2630..a0cfab4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -23,12 +23,13 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -40,9 +41,10 @@
 @RunWith(AndroidJUnit4::class)
 class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
 
-    private val dispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-    private val secureSettings = FakeSettings()
+    private val kosmos = testKosmos()
+    private val dispatcher = kosmos.testDispatcher
+    private val testScope = kosmos.testScope
+    private val secureSettings = kosmos.fakeSettings
     private val userRepository = Kosmos().fakeUserRepository
     private lateinit var repository: UserAwareSecureSettingsRepository
 
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/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 6e39365..3e7980d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -184,11 +184,11 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
-import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
similarity index 76%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
copy to packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
index 3c5beeb..6251ae9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/packages/SystemUI/tests/utils/src/android/app/StatusBarManagerKosmos.kt
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package android.app
 
-parcelable BubbleBarLocation;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.statusBarManager by Kosmos.Fixture { mock<StatusBarManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt
similarity index 72%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt
index d60f14c..8541d77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume
+package com.android.settingslib.notification.modes
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.google.common.util.concurrent.MoreExecutors
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+val Kosmos.zenIconLoader by Fixture { ZenIconLoader(MoreExecutors.newDirectExecutorService()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
deleted file mode 100644
index 60d97d1..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
+++ /dev/null
@@ -1,22 +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.bouncer.shared.flag
-
-import com.android.systemui.kosmos.Kosmos
-
-var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() }
-val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
deleted file mode 100644
index 7482c0f..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
+++ /dev/null
@@ -1,32 +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.bouncer.shared.flag
-
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-
-class FakeComposeBouncerFlags(var composeBouncerEnabled: Boolean = false) : ComposeBouncerFlags {
-    override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
-        return SceneContainerFlag.isEnabled || composeBouncerEnabled
-    }
-
-    @Deprecated(
-        "Avoid using this, this is meant to be used only by the glue code " +
-            "that includes compose bouncer in legacy keyguard.",
-        replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
-    )
-    override fun isOnlyComposeBouncerEnabled(): Boolean = composeBouncerEnabled
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index e8612d08..5c5969d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
-import com.android.systemui.bouncer.shared.flag.composeBouncerFlags
 import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
@@ -45,7 +44,6 @@
         faceAuthInteractor = deviceEntryFaceAuthInteractor,
         deviceUnlockedInteractor = deviceUnlockedInteractor,
         deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
-        flags = composeBouncerFlags,
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index e405d17..649e4e8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
-import com.android.systemui.bouncer.shared.flag.composeBouncerFlags
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -34,16 +33,16 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.StateFlow
 
-val Kosmos.bouncerSceneActionsViewModel by Fixture {
-    BouncerSceneActionsViewModel(
+val Kosmos.bouncerUserActionsViewModel by Fixture {
+    BouncerUserActionsViewModel(
         bouncerInteractor = bouncerInteractor,
     )
 }
 
-val Kosmos.bouncerSceneActionsViewModelFactory by Fixture {
-    object : BouncerSceneActionsViewModel.Factory {
-        override fun create(): BouncerSceneActionsViewModel {
-            return bouncerSceneActionsViewModel
+val Kosmos.bouncerUserActionsViewModelFactory by Fixture {
+    object : BouncerUserActionsViewModel.Factory {
+        override fun create(): BouncerUserActionsViewModel {
+            return bouncerUserActionsViewModel
         }
     }
 }
@@ -55,7 +54,6 @@
         authenticationInteractor = authenticationInteractor,
         devicePolicyManager = devicePolicyManager,
         bouncerMessageViewModelFactory = bouncerMessageViewModelFactory,
-        flags = composeBouncerFlags,
         userSwitcher = userSwitcherViewModel,
         actionButtonInteractor = bouncerActionButtonInteractor,
         pinViewModelFactory = pinBouncerViewModelFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt
index 86a8ae5..1ef3464 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/ConfigurationStateKosmos.kt
@@ -17,11 +17,8 @@
 package com.android.systemui.common.ui
 
 import android.content.applicationContext
-import android.view.layoutInflater
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.policy.configurationController
 
 val Kosmos.configurationState: ConfigurationState by
-    Kosmos.Fixture {
-        ConfigurationState(configurationController, applicationContext, layoutInflater)
-    }
+    Kosmos.Fixture { ConfigurationStateImpl(configurationController, applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index c00454f..5d7e7c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -30,7 +30,7 @@
     override fun addWidget(
         provider: ComponentName,
         user: UserHandle,
-        priority: Int,
+        rank: Int?,
         configurator: WidgetConfigurator?
     ) {
         coroutineScope.launch {
@@ -38,7 +38,7 @@
             val providerInfo = AppWidgetProviderInfo().apply { this.provider = provider }
             val configured = configurator?.configureWidget(id) ?: true
             if (configured) {
-                onConfigured(id, providerInfo, priority)
+                onConfigured(id, providerInfo, rank ?: -1)
             }
         }
     }
@@ -46,14 +46,14 @@
     fun addWidget(
         appWidgetId: Int,
         componentName: String = "pkg/cls",
-        priority: Int = 0,
+        rank: Int = 0,
         category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
         userId: Int = 0,
     ) {
         fakeDatabase[appWidgetId] =
             CommunalWidgetContentModel.Available(
                 appWidgetId = appWidgetId,
-                priority = priority,
+                rank = rank,
                 providerInfo =
                     AppWidgetProviderInfo().apply {
                         provider = ComponentName.unflattenFromString(componentName)!!
@@ -73,14 +73,14 @@
     fun addPendingWidget(
         appWidgetId: Int,
         componentName: String = "pkg/cls",
-        priority: Int = 0,
+        rank: Int = 0,
         icon: Bitmap? = null,
         userId: Int = 0,
     ) {
         fakeDatabase[appWidgetId] =
             CommunalWidgetContentModel.Pending(
                 appWidgetId = appWidgetId,
-                priority = priority,
+                rank = rank,
                 componentName = ComponentName.unflattenFromString(componentName)!!,
                 icon = icon,
                 user = UserHandle(userId),
@@ -97,8 +97,8 @@
 
     override fun abortRestoreWidgets() {}
 
-    private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
+    private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, rank: Int) {
         _communalWidgets.value +=
-            listOf(CommunalWidgetContentModel.Available(id, providerInfo, priority))
+            listOf(CommunalWidgetContentModel.Available(id, providerInfo, rank))
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 4ad046c..629fda6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.phone.fakeManagedProfileController
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.mock
 
@@ -61,6 +62,7 @@
         sceneInteractor = sceneInteractor,
         logBuffer = logcatLogBuffer("CommunalInteractor"),
         tableLogBuffer = mock(),
+        managedProfileController = fakeManagedProfileController
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index b9be04d..3dfe0ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,6 +58,7 @@
             powerInteractor = powerInteractor,
             biometricSettingsRepository = biometricSettingsRepository,
             trustManager = trustManager,
+            sceneInteractor = { sceneInteractor },
             deviceEntryFaceAuthStatusInteractor = deviceEntryFaceAuthStatusInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index caa6e99..13116e7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
+import com.android.systemui.keyguard.dismissCallbackRegistry
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,5 +35,6 @@
             sceneInteractor = sceneInteractor,
             deviceUnlockedInteractor = deviceUnlockedInteractor,
             alternateBouncerInteractor = alternateBouncerInteractor,
+            dismissCallbackRegistry = dismissCallbackRegistry,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index cdfb297..fb4e2fb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.education.data.repository
 
 import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.education.data.model.EduDeviceConnectionTime
 import com.android.systemui.education.data.model.GestureEduModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -27,22 +28,34 @@
     private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
     private val _gestureEduModels = MutableStateFlow(GestureEduModel(userId = 0))
     private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+
+    private val userEduDeviceConnectionTimeMap = mutableMapOf<Int, EduDeviceConnectionTime>()
+    private val _eduDeviceConnectionTime = MutableStateFlow(EduDeviceConnectionTime())
+    private val eduDeviceConnectionTime = _eduDeviceConnectionTime.asStateFlow()
+
     private var currentUser: Int = 0
 
     override fun setUser(userId: Int) {
         if (!userGestureMap.contains(userId)) {
             userGestureMap[userId] = GestureEduModel(userId = userId)
+            userEduDeviceConnectionTimeMap[userId] = EduDeviceConnectionTime()
         }
         // save data of current user to the map
         userGestureMap[currentUser] = _gestureEduModels.value
+        userEduDeviceConnectionTimeMap[currentUser] = _eduDeviceConnectionTime.value
         // switch to data of new user
         _gestureEduModels.value = userGestureMap[userId]!!
+        _eduDeviceConnectionTime.value = userEduDeviceConnectionTimeMap[userId]!!
     }
 
     override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> {
         return gestureEduModelsFlow
     }
 
+    override fun readEduDeviceConnectionTime(): Flow<EduDeviceConnectionTime> {
+        return eduDeviceConnectionTime
+    }
+
     override suspend fun updateGestureEduModel(
         gestureType: GestureType,
         transform: (GestureEduModel) -> GestureEduModel
@@ -50,4 +63,11 @@
         val currentModel = _gestureEduModels.value
         _gestureEduModels.value = transform(currentModel)
     }
+
+    override suspend fun updateEduDeviceConnectionTime(
+        transform: (EduDeviceConnectionTime) -> EduDeviceConnectionTime
+    ) {
+        val currentModel = _eduDeviceConnectionTime.value
+        _eduDeviceConnectionTime.value = transform(currentModel)
+    }
 }
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 5088677..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,19 +16,36 @@
 
 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
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
 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 {
         KeyboardTouchpadEduInteractor(
             backgroundScope = testScope.backgroundScope,
             contextualEducationInteractor = contextualEducationInteractor,
-            clock = fakeEduClock
+            userInputDeviceRepository =
+                UserInputDeviceRepository(
+                    testDispatcher,
+                    keyboardRepository,
+                    touchpadRepository,
+                    userRepository
+                ),
+            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/VibratorHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt
index 434953f..ff71f2f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt
@@ -17,5 +17,7 @@
 package com.android.systemui.haptics
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.VibratorHelper
 
-var Kosmos.vibratorHelper by Kosmos.Fixture { FakeVibratorHelper() }
+var Kosmos.vibratorHelper: VibratorHelper by Kosmos.Fixture { fakeVibratorHelper }
+val Kosmos.fakeVibratorHelper by Kosmos.Fixture { FakeVibratorHelper() }
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/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
similarity index 80%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
index 3c5beeb..f5a05b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common.bubbles;
+package com.android.systemui.haptics.msdl
 
-parcelable BubbleBarLocation;
\ No newline at end of file
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.msdlPlayer by Kosmos.Fixture { FakeMSDLPlayer() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index e96aeada..5753c6c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -27,7 +27,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.filterNotNull
 
 @SysUISingleton
@@ -37,38 +36,41 @@
     private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
     override val authenticationStatus: Flow<FaceAuthenticationStatus> =
         _authenticationStatus.filterNotNull()
+
     fun setAuthenticationStatus(status: FaceAuthenticationStatus) {
         _authenticationStatus.value = status
     }
+
     private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
     override val detectionStatus: Flow<FaceDetectionStatus>
         get() = _detectionStatus.filterNotNull()
+
     fun setDetectionStatus(status: FaceDetectionStatus) {
         _detectionStatus.value = status
     }
 
     private val _isLockedOut = MutableStateFlow(false)
     override val isLockedOut = _isLockedOut
-    private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
-    val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
-        _runningAuthRequest.asStateFlow()
+    val runningAuthRequest: MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
+        MutableStateFlow(null)
 
     private val _isAuthRunning = MutableStateFlow(false)
     override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
 
     override val isBypassEnabled = MutableStateFlow(false)
+
     override fun setLockedOut(isLockedOut: Boolean) {
         _isLockedOut.value = isLockedOut
     }
 
     override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
-        _runningAuthRequest.value = uiEvent to fallbackToDetection
+        runningAuthRequest.value = uiEvent to fallbackToDetection
         _isAuthRunning.value = true
     }
 
     override fun cancel() {
         _isAuthRunning.value = false
-        _runningAuthRequest.value = null
+        runningAuthRequest.value = null
     }
 }
 
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/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index a95609e..f5232ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -54,6 +54,7 @@
         sceneInteractor: SceneInteractor = mock(),
         fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(),
         fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor = mock(),
+        fromOccludedTransitionInteractor: FromOccludedTransitionInteractor = mock(),
         sharedNotificationContainerInteractor: SharedNotificationContainerInteractor? = null,
         powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
         testScope: CoroutineScope = TestScope(),
@@ -100,6 +101,7 @@
                 sceneInteractorProvider = { sceneInteractor },
                 fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
                 fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+                fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
                 sharedNotificationContainerInteractor = { sncInteractor },
                 applicationScope = testScope,
             ),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 5ab56e9..e85114d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -38,6 +38,7 @@
             sceneInteractorProvider = { sceneInteractor },
             fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
             fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+            fromOccludedTransitionInteractor = { fromOccludedTransitionInteractor },
             sharedNotificationContainerInteractor = { sharedNotificationContainerInteractor },
             applicationScope = testScope.backgroundScope,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 73799b6..769612c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import android.view.accessibility.accessibilityManagerWrapper
 import com.android.internal.logging.uiEventLogger
 import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
@@ -38,5 +39,6 @@
             broadcastDispatcher = broadcastDispatcher,
             accessibilityManager = accessibilityManagerWrapper,
             pulsingGestureListener = pulsingGestureListener,
+            faceAuthInteractor = deviceEntryFaceAuthInteractor,
         )
     }
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/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index b9443bc..7cf4083 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
 import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -39,6 +40,7 @@
         communalInteractor = communalInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         notificationsKeyguardInteractor = notificationsKeyguardInteractor,
+        notificationShadeWindowModel = notificationShadeWindowModel,
         alternateBouncerToAodTransitionViewModel = alternateBouncerToAodTransitionViewModel,
         alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
         alternateBouncerToLockscreenTransitionViewModel =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 19b32bc..f47b2df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.biometrics.authController
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
 import com.android.systemui.kosmos.Kosmos
@@ -34,5 +35,6 @@
             shadeInteractor = shadeInteractor,
             unfoldTransitionInteractor = unfoldTransitionInteractor,
             occlusionInteractor = sceneContainerOcclusionInteractor,
+            deviceEntryInteractor = deviceEntryInteractor,
         )
     }
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/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index b34681a..f8df707 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -5,9 +5,12 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 
 var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
+var Kosmos.unconfinedTestDispatcher by Fixture { UnconfinedTestDispatcher() }
 var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
+var Kosmos.unconfinedTestScope by Fixture { TestScope(unconfinedTestDispatcher) }
 var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
 var Kosmos.testCase: SysuiTestCase by Fixture()
 var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 9fe66eb..851a378 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -36,12 +36,14 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.globalactions.domain.interactor.globalActionsInteractor
+import com.android.systemui.haptics.msdl.msdlPlayer
 import com.android.systemui.haptics.qs.qsLongPressEffect
 import com.android.systemui.jank.interactionJankMonitor
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.fromGoneTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.fromOccludedTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -128,6 +130,7 @@
     val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor }
     val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository }
     val fromLockscreenTransitionInteractor by lazy { kosmos.fromLockscreenTransitionInteractor }
+    val fromOccludedTransitionInteractor by lazy { kosmos.fromOccludedTransitionInteractor }
     val fromPrimaryBouncerTransitionInteractor by lazy {
         kosmos.fromPrimaryBouncerTransitionInteractor
     }
@@ -152,4 +155,5 @@
     val scrimController by lazy { kosmos.scrimController }
     val scrimStartable by lazy { kosmos.scrimStartable }
     val sceneContainerOcclusionInteractor by lazy { kosmos.sceneContainerOcclusionInteractor }
+    val msdlPlayer by lazy { kosmos.msdlPlayer }
 }
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/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/table/TableLogBufferFactoryKosmos.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/log/table/TableLogBufferFactoryKosmos.kt
index 5d70ed6..c55dc6a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/table/TableLogBufferFactoryKosmos.kt
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.log.table
 
+import com.android.systemui.dump.dumpManager
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.log.LogcatEchoTrackerAlways
+import com.android.systemui.util.time.fakeSystemClock
 
-val Kosmos.quickSettingsShadeSceneActionsViewModel: QuickSettingsShadeSceneActionsViewModel by
+val Kosmos.tableLogBufferFactory: TableLogBufferFactory by
     Kosmos.Fixture {
-        QuickSettingsShadeSceneActionsViewModel(
-            shadeInteractor = shadeInteractor,
-            quickSettingsContainerViewModel = quickSettingsContainerViewModel,
+        TableLogBufferFactory(
+            dumpManager = dumpManager,
+            systemClock = fakeSystemClock,
+            logcatEchoTracker = LogcatEchoTrackerAlways(),
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/table/TableLogBufferHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/table/TableLogBufferHelper.kt
new file mode 100644
index 0000000..64c3974
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/table/TableLogBufferHelper.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.log.table
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.LogcatEchoTrackerAlways
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.time.fakeSystemClock
+
+/**
+ * Creates a [TableLogBuffer] that will echo everything to logcat, which is useful for debugging
+ * tests.
+ */
+fun logcatTableLogBuffer(kosmos: Kosmos, name: String = "EchoToLogcatTableLogBuffer") =
+    logcatTableLogBuffer(kosmos.fakeSystemClock, name)
+
+/**
+ * Creates a [TableLogBuffer] that will echo everything to logcat, which is useful for debugging
+ * tests.
+ */
+fun logcatTableLogBuffer(systemClock: SystemClock, name: String = "EchoToLogcatTableLogBuffer") =
+    TableLogBuffer(maxSize = 50, name, systemClock, logcatEchoTracker = LogcatEchoTrackerAlways())
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/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
new file mode 100644
index 0000000..a5690a0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.app.statusBarManager
+import android.content.testableContext
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.mediaFlags
+import com.android.systemui.plugins.activityStarter
+
+val Kosmos.mediaDataLoader by
+    Kosmos.Fixture {
+        MediaDataLoader(
+            testableContext,
+            testDispatcher,
+            testScope,
+            activityStarter,
+            fakeMediaControllerFactory,
+            mediaFlags,
+            imageLoader,
+            statusBarManager
+        )
+    }
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 cc1ad1f..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
@@ -28,7 +27,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.media.controls.data.repository.mediaDataRepository
 import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
 import com.android.systemui.media.controls.util.mediaFlags
 import com.android.systemui.media.controls.util.mediaUiEventLogger
 import com.android.systemui.plugins.activityStarter
@@ -45,8 +44,8 @@
             backgroundExecutor = fakeExecutor,
             uiExecutor = fakeExecutor,
             foregroundExecutor = fakeExecutor,
-            handler = fakeExecutorHandler,
-            mediaControllerFactory = mediaControllerFactory,
+            mainDispatcher = testDispatcher,
+            mediaControllerFactory = fakeMediaControllerFactory,
             broadcastDispatcher = broadcastDispatcher,
             dumpManager = dumpManager,
             activityStarter = activityStarter,
@@ -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 b98f557..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
@@ -22,8 +22,8 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
 import com.android.systemui.media.controls.util.localMediaManagerFactory
-import com.android.systemui.media.controls.util.mediaControllerFactory
 import com.android.systemui.media.muteawait.mediaMuteAwaitConnectionManagerFactory
 import com.android.systemui.statusbar.policy.configurationController
 
@@ -31,7 +31,7 @@
     Kosmos.Fixture {
         MediaDeviceManager(
             context = applicationContext,
-            controllerFactory = mediaControllerFactory,
+            controllerFactory = fakeMediaControllerFactory,
             localMediaManagerFactory = localMediaManagerFactory,
             mr2manager = { MediaRouter2Manager.getInstance(applicationContext) },
             muteAwaitConnectionManagerFactory = mediaMuteAwaitConnectionManagerFactory,
@@ -41,5 +41,6 @@
             },
             fgExecutor = fakeExecutor,
             bgExecutor = fakeExecutor,
+            logger = mediaDeviceLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
index 6ec6378..b7660e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerKosmos.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.media.controls.util.mediaControllerFactory
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
 import com.android.systemui.media.controls.util.mediaFlags
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.util.time.systemClock
@@ -27,7 +27,7 @@
 val Kosmos.mediaTimeoutListener by
     Kosmos.Fixture {
         MediaTimeoutListener(
-            mediaControllerFactory = mediaControllerFactory,
+            mediaControllerFactory = fakeMediaControllerFactory,
             mainExecutor = fakeExecutor,
             logger = MediaTimeoutLogger(logcatLogBuffer("MediaTimeoutLogBuffer")),
             statusBarStateController = statusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
new file mode 100644
index 0000000..7f8348e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.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.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.MediaSession.Token
+
+class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
+
+    private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+
+    override fun create(token: MediaSession.Token): android.media.session.MediaController {
+        if (token !in mediaControllersForToken) {
+            super.create(token)
+        }
+        return mediaControllersForToken[token]!!
+    }
+
+    fun setControllerForToken(token: Token, mediaController: MediaController) {
+        mediaControllersForToken[token] = mediaController
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
index 1ce6e82..7ee58fa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaControllerFactoryKosmos.kt
@@ -19,4 +19,5 @@
 import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.mediaControllerFactory by Kosmos.Fixture { MediaControllerFactory(applicationContext) }
+val Kosmos.fakeMediaControllerFactory by
+    Kosmos.Fixture { FakeMediaControllerFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt
index d60f14c..8fc40e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeControllerCollectorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelKosmos.kt
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume
+package com.android.systemui.qs.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.Kosmos.Fixture
 
-val Kosmos.volumeControllerCollector by
-    Kosmos.Fixture { VolumeControllerCollector(applicationCoroutineScope) }
+val Kosmos.quickSettingsShadeOverlayActionsViewModel:
+    QuickSettingsShadeOverlayActionsViewModel by Fixture {
+    QuickSettingsShadeOverlayActionsViewModel()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index 5d70ed6..ff8b478 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -17,12 +17,14 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
 
-val Kosmos.quickSettingsShadeSceneActionsViewModel: QuickSettingsShadeSceneActionsViewModel by
+val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayContentViewModel by
     Kosmos.Fixture {
-        QuickSettingsShadeSceneActionsViewModel(
-            shadeInteractor = shadeInteractor,
+        QuickSettingsShadeOverlayContentViewModel(
+            sceneInteractor = sceneInteractor,
+            shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
             quickSettingsContainerViewModel = quickSettingsContainerViewModel,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
index 5ad5cb2..cd1704c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneContentViewModelKosmos.kt
@@ -14,18 +14,13 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.qs.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModelFactory
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 val Kosmos.quickSettingsShadeSceneContentViewModel: QuickSettingsShadeSceneContentViewModel by
     Kosmos.Fixture {
         QuickSettingsShadeSceneContentViewModel(
-            overlayShadeViewModelFactory = overlayShadeViewModelFactory,
             quickSettingsContainerViewModel = quickSettingsContainerViewModel,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
similarity index 76%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
index 5d70ed6..06592b1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeUserActionsViewModelKosmos.kt
@@ -17,12 +17,10 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.domain.interactor.shadeInteractor
 
-val Kosmos.quickSettingsShadeSceneActionsViewModel: QuickSettingsShadeSceneActionsViewModel by
+val Kosmos.quickSettingsShadeUserActionsViewModel: QuickSettingsShadeUserActionsViewModel by
     Kosmos.Fixture {
-        QuickSettingsShadeSceneActionsViewModel(
-            shadeInteractor = shadeInteractor,
+        QuickSettingsShadeUserActionsViewModel(
             quickSettingsContainerViewModel = quickSettingsContainerViewModel,
         )
     }
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..b3664e1 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
@@ -2,9 +2,10 @@
 
 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.Overlays
 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(
@@ -17,11 +18,19 @@
     )
 }
 
-val Kosmos.fakeScenes by Fixture { sceneKeys.map { key -> FakeScene(key) }.toSet() }
-
-val Kosmos.scenes by Fixture { fakeScenes }
-
 val Kosmos.initialSceneKey by Fixture { Scenes.Lockscreen }
+
+var Kosmos.overlayKeys by Fixture {
+    listOf(
+        Overlays.NotificationsShade,
+        Overlays.QuickSettingsShade,
+    )
+}
+
+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 +41,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..c95b2dc 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
@@ -28,7 +29,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
 
 private val mutableTransitionState =
     MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(Scenes.Lockscreen))
@@ -36,20 +36,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 +65,7 @@
 ) {
     repository.setTransitionState(mutableTransitionState)
     mutableTransitionState.value = transition
-    scope.runCurrent()
+    scope.testScheduler.runCurrent()
 }
 
 fun Transition(
@@ -87,3 +93,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.fromContent != Scenes.Lockscreen) {
+                        KeyguardState.UNDEFINED
+                    } else {
+                        state.from
+                    },
+                to =
+                    if (sceneTransition.toContent != 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/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index ae8b411..f84c3bd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.scene.domain.resolver.sceneFamilyResolvers
 import com.android.systemui.scene.shared.logger.sceneLogger
 
-val Kosmos.sceneInteractor by
+val Kosmos.sceneInteractor: SceneInteractor by
     Kosmos.Fixture {
         SceneInteractor(
             applicationScope = applicationCoroutineScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 53b6a2e..b612a8b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -24,8 +24,10 @@
 import com.android.systemui.classifier.falsingCollector
 import com.android.systemui.classifier.falsingManager
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.keyguard.dismissCallbackRegistry
 import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -55,6 +57,7 @@
         applicationScope = testScope.backgroundScope,
         sceneInteractor = sceneInteractor,
         deviceEntryInteractor = deviceEntryInteractor,
+        deviceEntryHapticsInteractor = deviceEntryHapticsInteractor,
         deviceUnlockedInteractor = deviceUnlockedInteractor,
         bouncerInteractor = bouncerInteractor,
         keyguardInteractor = keyguardInteractor,
@@ -82,5 +85,6 @@
         dismissCallbackRegistry = dismissCallbackRegistry,
         statusBarStateController = sysuiStatusBarStateController,
         alternateBouncerInteractor = alternateBouncerInteractor,
+        vibratorHelper = vibratorHelper,
     )
 }
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
deleted file mode 100644
index 64e3526..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
+++ /dev/null
@@ -1,43 +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.scene.shared.model
-
-import com.android.compose.animation.scene.SceneKey
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.receiveAsFlow
-
-class FakeScene(
-    override val key: SceneKey,
-) : Scene {
-    var isDestinationScenesBeingCollected = false
-
-    private val destinationScenesChannel = Channel<Map<UserAction, UserActionResult>>()
-
-    override val destinationScenes =
-        destinationScenesChannel
-            .receiveAsFlow()
-            .onStart { isDestinationScenesBeingCollected = true }
-            .onCompletion { isDestinationScenesBeingCollected = false }
-
-    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/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 4660337..4a86fd5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -72,10 +72,6 @@
     @Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead")
     override val legacyLockscreenShadeTracking = MutableStateFlow(false)
 
-    private var _isDualShadeAlignedToBottom = false
-    override val isDualShadeAlignedToBottom
-        get() = _isDualShadeAlignedToBottom
-
     private var _isShadeLayoutWide = MutableStateFlow(false)
     override val isShadeLayoutWide: StateFlow<Boolean> = _isShadeLayoutWide.asStateFlow()
 
@@ -155,10 +151,6 @@
         _legacyShadeExpansion.value = expandedFraction
     }
 
-    fun setDualShadeAlignedToBottom(isAlignedToBottom: Boolean) {
-        _isDualShadeAlignedToBottom = isAlignedToBottom
-    }
-
     override fun setShadeLayoutWide(isShadeLayoutWide: Boolean) {
         _isShadeLayoutWide.value = isShadeLayoutWide
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayActionsViewModelKosmos.kt
similarity index 73%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayActionsViewModelKosmos.kt
index 9bf4756..1e00ac4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayActionsViewModelKosmos.kt
@@ -18,12 +18,9 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel
 
-val Kosmos.notificationsShadeSceneActionsViewModel:
-    NotificationsShadeSceneActionsViewModel by Fixture {
-    NotificationsShadeSceneActionsViewModel(
-        shadeInteractor = shadeInteractor,
-    )
+val Kosmos.notificationsShadeOverlayActionsViewModel:
+    NotificationsShadeOverlayActionsViewModel by Fixture {
+    NotificationsShadeOverlayActionsViewModel()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
new file mode 100644
index 0000000..9cdd519
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.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.shade.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory
+
+val Kosmos.notificationsShadeOverlayContentViewModel:
+    NotificationsShadeOverlayContentViewModel by Fixture {
+    NotificationsShadeOverlayContentViewModel(
+        shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
+        notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory,
+        sceneInteractor = sceneInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt
index 9bf4756..6345c40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeUserActionsViewModelKosmos.kt
@@ -18,12 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeUserActionsViewModel
 
-val Kosmos.notificationsShadeSceneActionsViewModel:
-    NotificationsShadeSceneActionsViewModel by Fixture {
-    NotificationsShadeSceneActionsViewModel(
-        shadeInteractor = shadeInteractor,
-    )
-}
+val Kosmos.notificationsShadeUserActionsViewModel:
+    NotificationsShadeUserActionsViewModel by Fixture { NotificationsShadeUserActionsViewModel() }
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..9742595 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,14 +16,18 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 
 val Kosmos.notificationShadeWindowModel: NotificationShadeWindowModel by
     Kosmos.Fixture {
         NotificationShadeWindowModel(
             keyguardTransitionInteractor,
-            keyguardInteractor,
+            sceneInteractor = { sceneInteractor },
+            authenticationInteractor = { authenticationInteractor },
+            primaryBouncerInteractor = primaryBouncerInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt
deleted file mode 100644
index 00f1526..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt
+++ /dev/null
@@ -1,38 +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.shade.ui.viewmodel
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-
-val Kosmos.overlayShadeViewModel: OverlayShadeViewModel by
-    Kosmos.Fixture {
-        OverlayShadeViewModel(
-            sceneInteractor = sceneInteractor,
-            shadeInteractor = shadeInteractor,
-        )
-    }
-
-val Kosmos.overlayShadeViewModelFactory: OverlayShadeViewModel.Factory by
-    Kosmos.Fixture {
-        object : OverlayShadeViewModel.Factory {
-            override fun create(): OverlayShadeViewModel {
-                return overlayShadeViewModel
-            }
-        }
-    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt
similarity index 89%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt
index 2387aa8..48c5121 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt
@@ -21,8 +21,8 @@
 import com.android.systemui.qs.ui.adapter.qsSceneAdapter
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 
-val Kosmos.shadeSceneActionsViewModel: ShadeSceneActionsViewModel by Fixture {
-    ShadeSceneActionsViewModel(
+val Kosmos.shadeUserActionsViewModel: ShadeUserActionsViewModel by Fixture {
+    ShadeUserActionsViewModel(
         qsSceneAdapter = qsSceneAdapter,
         shadeInteractor = shadeInteractor,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
index 5d70ed6..c0d65a0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel
 
+import android.content.packageManager
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.util.time.fakeSystemClock
 
-val Kosmos.quickSettingsShadeSceneActionsViewModel: QuickSettingsShadeSceneActionsViewModel by
+val Kosmos.demoRonChipViewModel: DemoRonChipViewModel by
     Kosmos.Fixture {
-        QuickSettingsShadeSceneActionsViewModel(
-            shadeInteractor = shadeInteractor,
-            quickSettingsContainerViewModel = quickSettingsContainerViewModel,
+        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/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.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/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
index 5d70ed6..14777b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/commandline/CommandRegistryKosmos.kt
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.ui.viewmodel
+package com.android.systemui.statusbar.commandline
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.shade.domain.interactor.shadeInteractor
 
-val Kosmos.quickSettingsShadeSceneActionsViewModel: QuickSettingsShadeSceneActionsViewModel by
+val Kosmos.commandRegistry: CommandRegistry by
     Kosmos.Fixture {
-        QuickSettingsShadeSceneActionsViewModel(
-            shadeInteractor = shadeInteractor,
-            quickSettingsContainerViewModel = quickSettingsContainerViewModel,
+        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/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 634354b..8bfc390 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -35,3 +35,11 @@
         dumpManager = dumpManager,
     )
 }
+
+val Kosmos.notificationsPlaceholderViewModelFactory by Fixture {
+    object : NotificationsPlaceholderViewModel.Factory {
+        override fun create(): NotificationsPlaceholderViewModel {
+            return notificationsPlaceholderViewModel
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt
index 9bf4756..ef04b9d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ManagedProfileControllerKosmos.kt
@@ -14,16 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shade.ui.viewmodel
+@file:OptIn(ExperimentalCoroutinesApi::class)
 
+package com.android.systemui.statusbar.phone
+
+import android.testing.LeakCheck
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneActionsViewModel
-import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.utils.leaks.FakeManagedProfileController
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-val Kosmos.notificationsShadeSceneActionsViewModel:
-    NotificationsShadeSceneActionsViewModel by Fixture {
-    NotificationsShadeSceneActionsViewModel(
-        shadeInteractor = shadeInteractor,
-    )
-}
+val Kosmos.fakeManagedProfileController by Fixture { FakeManagedProfileController(LeakCheck()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
index 386c7c5..d76defe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 
 val Kosmos.airplaneModeInteractor: AirplaneModeInteractor by
@@ -26,6 +26,6 @@
         AirplaneModeInteractor(
             FakeAirplaneModeRepository(),
             FakeConnectivityRepository(),
-            FakeMobileConnectionsRepository(),
+            fakeMobileConnectionsRepository,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 8229575..de73d33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -23,18 +23,16 @@
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
-import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
 class FakeMobileConnectionsRepository(
     mobileMappings: MobileMappingsProxy = FakeMobileMappingsProxy(),
-    val tableLogBuffer: TableLogBuffer = mock<TableLogBuffer> {},
+    val tableLogBuffer: TableLogBuffer,
 ) : MobileConnectionsRepository {
 
     val GSM_KEY = mobileMappings.toIconKey(GSM)
@@ -94,9 +92,10 @@
     private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
     override val defaultMobileIconGroup = _defaultMobileIconGroup
 
-    override val deviceServiceState = MutableStateFlow<ServiceStateModel?>(null)
+    override val isDeviceEmergencyCallCapable = MutableStateFlow(false)
 
     override val isAnySimSecure = MutableStateFlow(false)
+
     override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value
 
     private var isInEcmMode: Boolean = false
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
index 9d62ea5..cd22f1d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
@@ -18,10 +18,12 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.log.table.logcatTableLogBuffer
 
 val Kosmos.fakeMobileConnectionsRepository by Fixture {
-    FakeMobileConnectionsRepository(tableLogBuffer = mock())
+    FakeMobileConnectionsRepository(
+        tableLogBuffer = logcatTableLogBuffer(this, "FakeMobileConnectionsRepository"),
+    )
 }
 
 val Kosmos.mobileConnectionsRepository by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index aef0828..61b53c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -16,14 +16,20 @@
 
 package com.android.systemui.statusbar.policy.domain.interactor
 
+import android.content.testableContext
+import com.android.settingslib.notification.modes.zenIconLoader
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
 
 val Kosmos.zenModeInteractor by Fixture {
     ZenModeInteractor(
+        context = testableContext,
         zenModeRepository = zenModeRepository,
         notificationSettingsRepository = notificationSettingsRepository,
+        bgDispatcher = testDispatcher,
+        iconLoader = zenIconLoader,
     )
 }
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/util/settings/FakeGlobalSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
index 35fa2af..73d423c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
@@ -19,5 +19,10 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.unconfinedTestDispatcher
 
 val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings(testDispatcher) }
+
+val Kosmos.unconfinedDispatcherFakeGlobalSettings: FakeGlobalSettings by Fixture {
+    FakeGlobalSettings(unconfinedTestDispatcher)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
index 76ef202..e1daf9b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
@@ -19,8 +19,13 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.unconfinedTestDispatcher
 import com.android.systemui.settings.userTracker
 
 val Kosmos.fakeSettings: FakeSettings by Fixture {
     FakeSettings(testDispatcher) { userTracker.userId }
 }
+
+val Kosmos.unconfinedDispatcherFakeSettings: FakeSettings by Fixture {
+    FakeSettings(unconfinedTestDispatcher) { userTracker.userId }
+}
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..0089199 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,12 @@
     }
 
     @Override
+    public void setResourceIcon(String slot, @Nullable String resPackage, int iconResId,
+            @Nullable Drawable preloadedIcon, CharSequence contentDescription,
+            StatusBarIcon.Shape shape) {
+    }
+
+    @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..888351f 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,17 +18,27 @@
 
 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
 
 class FakeAudioRepository : AudioRepository {
 
+    private val unMutableStreams =
+        setOf(
+            AudioManager.STREAM_VOICE_CALL,
+            AudioManager.STREAM_ALARM,
+        )
+
     private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL)
     override val mode: StateFlow<Int> = mutableMode.asStateFlow()
 
@@ -39,10 +49,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> =
@@ -53,7 +79,7 @@
                     volume = 0,
                     minVolume = 0,
                     maxVolume = 10,
-                    isAffectedByMute = false,
+                    isAffectedByMute = audioStream.value !in unMutableStreams,
                     isAffectedByRingerMode = false,
                     isMuted = false,
                 )
@@ -111,4 +137,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/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
index 83adc79..ad1292e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt
@@ -38,6 +38,7 @@
     ): MediaDevice = mock {
         whenever(name).thenReturn(deviceName)
         whenever(icon).thenReturn(deviceIcon)
+        whenever(deviceType).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE)
     }
 
     fun wiredMediaDevice(
@@ -77,6 +78,18 @@
             whenever(name).thenReturn(deviceName)
             whenever(icon).thenReturn(deviceIcon)
             whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+            whenever(deviceType).thenReturn(MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE)
+        }
+    }
+
+    fun remoteMediaDevice(
+        deviceName: String = "remote_media",
+        deviceIcon: Drawable? = TestStubDrawable(),
+    ): MediaDevice {
+        return mock<MediaDevice> {
+            whenever(name).thenReturn(deviceName)
+            whenever(icon).thenReturn(deviceIcon)
+            whenever(deviceType).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE)
         }
     }
 }
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 58cd2e4..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"],
 }
@@ -331,6 +333,7 @@
     name: "ravenwood-runtime",
     data: [
         "framework-res",
+        "ravenwood-empty-res",
     ],
     libs: [
         "100-framework-minus-apex.ravenwood",
diff --git a/ravenwood/OWNERS b/ravenwood/OWNERS
index badfca0..f7b13d1 100644
--- a/ravenwood/OWNERS
+++ b/ravenwood/OWNERS
@@ -1,8 +1,8 @@
 set noparent
 
-jsharkey@google.com
-omakoto@google.com
-dplotnikov@google.com
+topjohnwu@google.com
+hackbod@google.com  #{LAST_RESORT_SUGGESTION}
+
 
 per-file ravenwood-annotation-allowed-classes.txt = dplotnikov@google.com
 per-file texts/ravenwood-annotation-allowed-classes.txt = dplotnikov@google.com
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index fbf27fa..469759b 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -1,20 +1,12 @@
-// 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" },
     { "name": "hoststubgentest" },
+    { "name": "hoststubgen-test-tiny-test" },
     { "name": "hoststubgen-invoke-test" },
-    {
-      "name": "RavenwoodMockitoTest_device"
-    },
-    {
-      "name": "RavenwoodBivalentTest_device"
-    },
-    {
-      "name": "RavenwoodResApkTest"
-    },
+    { "name": "RavenwoodMockitoTest_device" },
+    { "name": "RavenwoodBivalentTest_device" },
+
     // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
     {
       "name": "SystemUIGoogleTests",
@@ -42,6 +34,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
@@ -51,16 +150,21 @@
       "host": true
     },
     {
-      "name": "CtsUtilTestCasesRavenwood",
+      "name": "RavenwoodResApkTest",
       "host": true
     },
     {
-      "name": "RavenwoodCoreTest",
+      "name": "RavenwoodRuntimeTest",
       "host": true
     },
     {
-      "name": "RavenwoodBivalentTest",
+      "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..09a0aa8
--- /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}.
+     */
+    public 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.
+     */
+    public 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/RavenwoodNoRavenizerTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
new file mode 100644
index 0000000..9d878f4
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodNoRavenizerTest.java
@@ -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.ravenwoodtest.bivalenttest.ravenizer;
+
+import android.platform.test.annotations.NoRavenizer;
+import android.platform.test.ravenwood.RavenwoodAwareTestRunner.RavenwoodTestRunnerInitializing;
+
+import org.junit.Test;
+
+/**
+ * Test for {@link android.platform.test.annotations.NoRavenizer}
+ */
+@NoRavenizer
+public class RavenwoodNoRavenizerTest {
+    public static final String TAG = "RavenwoodNoRavenizerTest";
+
+    private static final CallTracker sCallTracker = new CallTracker();
+
+    /**
+     * With @NoRavenizer, this method shouldn't be called.
+     */
+    @RavenwoodTestRunnerInitializing
+    public static void ravenwoodRunnerInitializing() {
+        sCallTracker.incrementMethodCallCount();
+    }
+
+    /**
+     * Make sure ravenwoodRunnerInitializing() wasn't called.
+     */
+    @Test
+    public void testNotRavenized() {
+        sCallTracker.assertCalls(
+                "ravenwoodRunnerInitializing", 0
+        );
+    }
+}
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/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
new file mode 100644
index 0000000..7e396c2
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/ravenizer/RavenwoodSuiteTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * Test to make sure {@link Suite} works with the ravenwood test runner.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+        RavenwoodSuiteTest.Test1.class,
+        RavenwoodSuiteTest.Test2.class
+})
+public class RavenwoodSuiteTest {
+    public static final String TAG = "RavenwoodSuiteTest";
+
+    private static final CallTracker sCallTracker = new CallTracker();
+
+    @AfterClass
+    public static void afterClass() {
+        Log.i(TAG, "afterClass called");
+
+        sCallTracker.assertCallsOrDie(
+                "test1", 1,
+                "test2", 1
+        );
+    }
+
+    /**
+     * Workaround for the issue where tradefed won't think a class is a test class
+     * if it has a @RunWith but no @Test methods, even if it is a Suite.
+     */
+    @Test
+    public void testEmpty() {
+    }
+
+    public static class Test1 {
+        @Test
+        public void test1() {
+            sCallTracker.incrementMethodCallCount();
+        }
+    }
+
+    public static class Test2 {
+        @Test
+        public void test2() {
+            sCallTracker.incrementMethodCallCount();
+        }
+    }
+}
diff --git a/ravenwood/coretest/Android.bp b/ravenwood/coretest/Android.bp
deleted file mode 100644
index a78c5c1..0000000
--- a/ravenwood/coretest/Android.bp
+++ /dev/null
@@ -1,23 +0,0 @@
-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"],
-}
-
-android_ravenwood_test {
-    name: "RavenwoodCoreTest",
-
-    static_libs: [
-        "androidx.annotation_annotation",
-        "androidx.test.ext.junit",
-        "androidx.test.rules",
-    ],
-    srcs: [
-        "test/**/*.java",
-    ],
-    sdk_version: "test_current",
-    auto_gen_config: true,
-}
diff --git a/ravenwood/coretest/README.md b/ravenwood/coretest/README.md
deleted file mode 100644
index b60bfbf..0000000
--- a/ravenwood/coretest/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Ravenwood core test
-
-This test contains (non-bivalent) tests for Ravenwood itself -- e.g. tests for the ravenwood rules.
\ No newline at end of file
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
deleted file mode 100644
index f1e33cb..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
+++ /dev/null
@@ -1,53 +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.ravenwoodtest.coretest;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4; // Intentionally use the deprecated one.
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * Test for the test runner validator in RavenwoodRule.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestRunnerValidationTest {
-    // Note the following rules don't have a @Rule, because they need to be applied in a specific
-    // order. So we use a RuleChain instead.
-    private ExpectedException mThrown = ExpectedException.none();
-    private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    @Rule
-    public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
-    public RavenwoodTestRunnerValidationTest() {
-        Assume.assumeTrue(RavenwoodRule._$RavenwoodPrivate.isOptionalValidationEnabled());
-        // Because RavenwoodRule will throw this error before executing the test method,
-        // we can't do it in the test method itself.
-        // So instead, we initialize it here.
-        mThrown.expectMessage("Switch to androidx.test.ext.junit.runners.AndroidJUnit4");
-    }
-
-    @Test
-    public void testValidateTestRunner() {
-    }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java
deleted file mode 100644
index db95fad..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail01_Test.java
+++ /dev/null
@@ -1,51 +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.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_Fail01_Test {
-    private ExpectedException mThrown = ExpectedException.none();
-    private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    @Rule
-    public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
-    public RavenwoodTestMethodValidation_Fail01_Test() {
-        mThrown.expectMessage("Method setUp() doesn't have @Before");
-    }
-
-    @SuppressWarnings("JUnit4SetUpNotRun")
-    public void setUp() {
-    }
-
-    @Test
-    public void testEmpty() {
-    }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java
deleted file mode 100644
index ddc66c7..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail02_Test.java
+++ /dev/null
@@ -1,51 +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.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_Fail02_Test {
-    private ExpectedException mThrown = ExpectedException.none();
-    private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    @Rule
-    public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
-    public RavenwoodTestMethodValidation_Fail02_Test() {
-        mThrown.expectMessage("Method tearDown() doesn't have @After");
-    }
-
-    @SuppressWarnings("JUnit4TearDownNotRun")
-    public void tearDown() {
-    }
-
-    @Test
-    public void testEmpty() {
-    }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java
deleted file mode 100644
index ec8e907..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_Fail03_Test.java
+++ /dev/null
@@ -1,51 +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.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_Fail03_Test {
-    private ExpectedException mThrown = ExpectedException.none();
-    private final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    @Rule
-    public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood);
-
-    public RavenwoodTestMethodValidation_Fail03_Test() {
-        mThrown.expectMessage("Method testFoo() doesn't have @Test");
-    }
-
-    @SuppressWarnings("JUnit4TestNotRun")
-    public void testFoo() {
-    }
-
-    @Test
-    public void testEmpty() {
-    }
-}
diff --git a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java
deleted file mode 100644
index d952d07..0000000
--- a/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/methodvalidation/RavenwoodTestMethodValidation_OkTest.java
+++ /dev/null
@@ -1,56 +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.ravenwoodtest.coretest.methodvalidation;
-
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * RavenwoodRule has a validator to ensure "test-looking" methods have valid JUnit annotations.
- * This class contains tests for this validator.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodTestMethodValidation_OkTest {
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
-    @Before
-    public void setUp() {
-    }
-
-    @Before
-    public void testSetUp() {
-    }
-
-    @After
-    public void tearDown() {
-    }
-
-    @After
-    public void testTearDown() {
-    }
-
-    @Test
-    public void testEmpty() {
-    }
-}
diff --git a/ravenwood/empty-res/Android.bp b/ravenwood/empty-res/Android.bp
new file mode 100644
index 0000000..3af7690
--- /dev/null
+++ b/ravenwood/empty-res/Android.bp
@@ -0,0 +1,4 @@
+android_app {
+    name: "ravenwood-empty-res",
+    sdk_version: "current",
+}
diff --git a/ravenwood/empty-res/AndroidManifest.xml b/ravenwood/empty-res/AndroidManifest.xml
new file mode 100644
index 0000000..f73460b
--- /dev/null
+++ b/ravenwood/empty-res/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.ravenwood.emptyres">
+</manifest>
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..f237ba9
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.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 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.getJavaClass()
+                + " 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 3ea4cb7..a2088fd 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -16,9 +16,9 @@
 
 package android.platform.test.ravenwood;
 
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
 
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -39,24 +39,11 @@
 import com.android.internal.os.RuntimeInit;
 import com.android.server.LocalServices;
 
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
 import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executors;
@@ -109,10 +96,6 @@
 
         android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
         android.os.Binder.init$ravenwood();
-//        android.os.SystemProperties.init$ravenwood(
-//                rule.mSystemProperties.getValues(),
-//                rule.mSystemProperties.getKeyReadablePredicate(),
-//                rule.mSystemProperties.getKeyWritablePredicate());
         setSystemProperties(rule.mSystemProperties);
 
         ServiceManager.init$ravenwood();
@@ -131,11 +114,12 @@
 
         // TODO This should be integrated into LoadedApk
         final Supplier<Resources> resourcesSupplier = () -> {
-            final var resApkFile = new File(RAVENWOOD_RESOURCE_APK).getAbsoluteFile();
+            var resApkFile = new File(RAVENWOOD_RESOURCE_APK);
+            if (!resApkFile.isFile()) {
+                resApkFile = new File(RAVENWOOD_EMPTY_RESOURCES_APK);
+            }
             assertTrue(resApkFile.isFile());
-
-            final var res = resApkFile.getAbsolutePath();
-
+            final String res = resApkFile.getAbsolutePath();
             final var emptyPaths = new String[0];
 
             ResourcesManager.getInstance().initializeApplicationPaths(res, emptyPaths);
@@ -241,106 +225,6 @@
         }
     }
 
-    public static void validate(Statement base, Description description,
-            boolean enableOptionalValidation) {
-        validateTestRunner(base, description, enableOptionalValidation);
-        validateTestAnnotations(base, description, enableOptionalValidation);
-    }
-
-    private static void validateTestRunner(Statement base, Description description,
-            boolean shouldFail) {
-        final var testClass = description.getTestClass();
-        final var runWith = testClass.getAnnotation(RunWith.class);
-        if (runWith == null) {
-            return;
-        }
-
-        // Due to build dependencies, we can't directly refer to androidx classes here,
-        // so just check the class name instead.
-        if (runWith.value().getCanonicalName().equals("androidx.test.runner.AndroidJUnit4")) {
-            var message = "Test " + testClass.getCanonicalName() + " uses deprecated"
-                    + " test runner androidx.test.runner.AndroidJUnit4."
-                    + " Switch to androidx.test.ext.junit.runners.AndroidJUnit4.";
-            if (shouldFail) {
-                Assert.fail(message);
-            } else {
-                System.err.println("Warning: " + message);
-            }
-        }
-    }
-
-    /**
-     * @return if a method has any of annotations.
-     */
-    private static boolean hasAnyAnnotations(Method m, Class<? extends Annotation>... annotations) {
-        for (var anno : annotations) {
-            if (m.getAnnotation(anno) != null) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static void validateTestAnnotations(Statement base, Description description,
-            boolean enableOptionalValidation) {
-        final var testClass = description.getTestClass();
-
-        final var message = new StringBuilder();
-
-        boolean hasErrors = false;
-        for (Method m : collectMethods(testClass)) {
-            if (Modifier.isPublic(m.getModifiers()) && m.getName().startsWith("test")) {
-                if (!hasAnyAnnotations(m, Test.class, Before.class, After.class,
-                        BeforeClass.class, AfterClass.class)) {
-                    message.append("\nMethod " + m.getName() + "() doesn't have @Test");
-                    hasErrors = true;
-                }
-            }
-            if ("setUp".equals(m.getName())) {
-                if (!hasAnyAnnotations(m, Before.class)) {
-                    message.append("\nMethod " + m.getName() + "() doesn't have @Before");
-                    hasErrors = true;
-                }
-                if (!Modifier.isPublic(m.getModifiers())) {
-                    message.append("\nMethod " + m.getName() + "() must be public");
-                    hasErrors = true;
-                }
-            }
-            if ("tearDown".equals(m.getName())) {
-                if (!hasAnyAnnotations(m, After.class)) {
-                    message.append("\nMethod " + m.getName() + "() doesn't have @After");
-                    hasErrors = true;
-                }
-                if (!Modifier.isPublic(m.getModifiers())) {
-                    message.append("\nMethod " + m.getName() + "() must be public");
-                    hasErrors = true;
-                }
-            }
-        }
-        assertFalse("Problem(s) detected in class " + testClass.getCanonicalName() + ":"
-                + message, hasErrors);
-    }
-
-    /**
-     * Collect all (public or private or any) methods in a class, including inherited methods.
-     */
-    private static List<Method> collectMethods(Class<?> clazz) {
-        var ret = new ArrayList<Method>();
-        collectMethods(clazz, ret);
-        return ret;
-    }
-
-    private static void collectMethods(Class<?> clazz, List<Method> result) {
-        // Class.getMethods() only return public methods, so we need to use getDeclaredMethods()
-        // instead, and recurse.
-        for (var m : clazz.getDeclaredMethods()) {
-            result.add(m);
-        }
-        if (clazz.getSuperclass() != null) {
-            collectMethods(clazz.getSuperclass(), result);
-        }
-    }
-
     /**
      * 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/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/LargeTest.java b/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java
similarity index 62%
rename from tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/LargeTest.java
rename to ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java
index 76bbcad..a84f16f 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/LargeTest.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/NoRavenizer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,16 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.hoststubgen.test.tinyframework;
+
+package android.platform.test.annotations;
 
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 /**
- * We don't want to use any android classes in this module, so we create our own copy of it here.
+ * Disable the ravenizer preprocessor for a class. This should be only used for testing
+ * ravenizer itself, or to workaround issues with the preprocessor. A test class probably won't run
+ * properly if it's not preprocessed.
+ *
+ * @hide
  */
+@Inherited
+@Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
-public @interface LargeTest {}
+public @interface NoRavenizer {
+}
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..7d99166
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -0,0 +1,412 @@
+/*
+ * 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 android.util.Log;
+
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+import com.android.ravenwood.common.SneakyThrow;
+
+import org.junit.Assume;
+import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
+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.Failure;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.RunnerBuilder;
+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.
+ *
+ * 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 TestClass mTestClass = null;
+    private Runner mRealRunner = null;
+    private Description mDescription = null;
+    private Throwable mExceptionInConstructor = null;
+
+    /** Simple logging method. */
+    private void log(String message) {
+        RavenwoodCommonUtils.log(TAG, "[" + getTestClass().getJavaClass() + "  @" + this + "] "
+                + message);
+    }
+
+    private Error logAndFail(String message, Throwable innerException) {
+        log(message);
+        log("    Exception=" + innerException);
+        throw new AssertionError(message, innerException);
+    }
+
+    public TestClass getTestClass() {
+        return mTestClass;
+    }
+
+    /**
+     * Constructor.
+     */
+    public RavenwoodAwareTestRunner(Class<?> testClass) {
+        try {
+            mTestClass = new TestClass(testClass);
+
+            /*
+             * If the class has @DisabledOnRavenwood, then we'll delegate to
+             * ClassSkippingTestRunner, which simply skips it.
+             */
+            if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood(
+                    mTestClass.getJavaClass())) {
+                mRealRunner = new ClassSkippingTestRunner(mTestClass);
+                mDescription = mRealRunner.getDescription();
+                return;
+            }
+
+            // Find the real runner.
+            final Class<? extends Runner> realRunnerClass;
+            final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class);
+            if (innerRunnerAnnotation != null) {
+                realRunnerClass = innerRunnerAnnotation.value();
+            } else {
+                // Default runner.
+                realRunnerClass = BlockJUnit4ClassRunner.class;
+            }
+
+            onRunnerInitializing();
+
+            try {
+                log("Initializing the inner runner: " + realRunnerClass);
+
+                mRealRunner = instantiateRealRunner(realRunnerClass, testClass);
+                mDescription = mRealRunner.getDescription();
+
+            } catch (InstantiationException | IllegalAccessException
+                     | InvocationTargetException | NoSuchMethodException e) {
+                throw logAndFail("Failed to instantiate " + realRunnerClass, e);
+            }
+        } catch (Throwable th) {
+            // If we throw in the constructor, Tradefed may not report it and just ignore the class,
+            // so record it and throw it when the test actually started.
+            log("Fatal: Exception detected in constructor: " + th.getMessage() + "\n"
+                    + Log.getStackTraceString(th));
+            mExceptionInConstructor = new RuntimeException("Exception detected in constructor",
+                    th);
+            mDescription = Description.createTestDescription(testClass, "Constructor");
+
+            // This is for testing if tradefed is fixed.
+            if ("1".equals(System.getenv("RAVENWOOD_THROW_EXCEPTION_IN_TEST_RUNNER"))) {
+                throw th;
+            }
+        }
+    }
+
+    private static Runner instantiateRealRunner(
+            Class<? extends Runner> realRunnerClass,
+            Class<?> testClass)
+            throws NoSuchMethodException, InvocationTargetException, InstantiationException,
+            IllegalAccessException {
+        try {
+            return realRunnerClass.getConstructor(Class.class).newInstance(testClass);
+        } catch (NoSuchMethodException e) {
+            var runnerBuilder = new AllDefaultPossibilitiesBuilder();
+            return realRunnerClass.getConstructor(Class.class,
+                    RunnerBuilder.class).newInstance(testClass, runnerBuilder);
+        }
+    }
+
+    /**
+     * 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, mTestClass);
+
+        // 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 mDescription;
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        if (mRealRunner instanceof ClassSkippingTestRunner) {
+            mRealRunner.run(notifier);
+            RavenwoodAwareTestRunnerHook.onClassSkipped(getDescription());
+            return;
+        }
+
+        if (maybeReportExceptionFromConstructor(notifier)) {
+            return;
+        }
+
+        sCurrentRunner.set(this);
+        try {
+            runWithHooks(getDescription(), Scope.Runner, Order.First,
+                    () -> mRealRunner.run(notifier));
+        } finally {
+            sCurrentRunner.remove();
+        }
+    }
+
+    /** Throw the exception detected in the constructor, if any. */
+    private boolean maybeReportExceptionFromConstructor(RunNotifier notifier) {
+        if (mExceptionInConstructor == null) {
+            return false;
+        }
+        notifier.fireTestStarted(mDescription);
+        notifier.fireTestFailure(new Failure(mDescription, mExceptionInConstructor));
+        notifier.fireTestFinished(mDescription);
+
+        return true;
+    }
+
+    @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 1298023..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 {
@@ -42,12 +44,17 @@
 
     private static final boolean IS_ON_RAVENWOOD = RavenwoodDivergence.isOnRavenwood();
 
-    private static final String RAVEWOOD_RUNTIME_PATH = getRavenwoodRuntimePathInternal();
+    private static final String RAVENWOOD_RUNTIME_PATH = getRavenwoodRuntimePathInternal();
 
     public static final String RAVENWOOD_SYSPROP = "ro.is_on_ravenwood";
 
     public static final String RAVENWOOD_RESOURCE_APK = "ravenwood-res-apks/ravenwood-res.apk";
 
+    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;
 
@@ -74,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.
      */
@@ -178,7 +197,7 @@
      */
     public static String getRavenwoodRuntimePath() {
         ensureOnRavenwood();
-        return RAVEWOOD_RUNTIME_PATH;
+        return RAVENWOOD_RUNTIME_PATH;
     }
 
     private static String getRavenwoodRuntimePathInternal() {
@@ -233,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/ParcelFileDescriptor_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
deleted file mode 100644
index 5a3589d..0000000
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
+++ /dev/null
@@ -1,31 +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.nativesubstitution;
-
-import com.android.ravenwood.common.JvmWorkaround;
-
-import java.io.FileDescriptor;
-
-public class ParcelFileDescriptor_host {
-    public static void setFdInt(FileDescriptor fd, int fdInt) {
-        JvmWorkaround.getInstance().setFdInt(fd, fdInt);
-    }
-
-    public static int getFdInt(FileDescriptor fd) {
-        return JvmWorkaround.getInstance().getFdInt(fd);
-    }
-}
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..58f6bbb 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
@@ -36,7 +36,10 @@
     /**
      * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
      */
-    public static void ensureRavenwoodInitialized() {
+    public static void nativeEnsureRavenwoodInitialized() {
+
+        // TODO Unify it with the initialization code in RavenwoodAwareTestRunnerHook.
+
         synchronized (sInitializeLock) {
             if (sInitialized) {
                 return;
@@ -60,14 +63,14 @@
     /**
      * Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}.
      */
-    public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) {
+    public static String nativeGetRavenwoodRuntimePath(RavenwoodEnvironment env) {
         return RavenwoodCommonUtils.getRavenwoodRuntimePath();
     }
 
     /**
      * Called from {@link RavenwoodEnvironment#fromAddress(long)}.
      */
-    public static <T> T fromAddress(RavenwoodEnvironment env, long address) {
+    public static <T> T nativeFromAddress(RavenwoodEnvironment env, long address) {
         return JvmWorkaround.getInstance().fromAddress(address);
     }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index 7371d0a..a5c0b54 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -15,8 +15,8 @@
  */
 package android.system;
 
+import com.android.ravenwood.RavenwoodRuntimeNative;
 import com.android.ravenwood.common.JvmWorkaround;
-import com.android.ravenwood.common.RavenwoodRuntimeNative;
 
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java
new file mode 100644
index 0000000..96aed4b
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodJdkPatch.java
@@ -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.ravenwood;
+
+import com.android.ravenwood.common.JvmWorkaround;
+
+import java.io.FileDescriptor;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Class to host APIs that exist in libcore, but not in standard JRE.
+ */
+public class RavenwoodJdkPatch {
+    /**
+     * Implements FileDescriptor.getInt$()
+     */
+    public static int getInt$(FileDescriptor fd) {
+        return JvmWorkaround.getInstance().getFdInt(fd);
+    }
+
+    /**
+     * Implements FileDescriptor.setInt$(int)
+     */
+    public static void setInt$(FileDescriptor fd, int rawFd) {
+        JvmWorkaround.getInstance().setFdInt(fd, rawFd);
+    }
+
+    /**
+     * Implements LinkedHashMap.eldest()
+     */
+    public static <K, V> Map.Entry<K, V> eldest(LinkedHashMap<K, V> map) {
+        final var it = map.entrySet().iterator();
+        return it.hasNext() ? it.next() : null;
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
similarity index 95%
rename from ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
rename to ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index beba833..0d8408c 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/common/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -13,11 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.ravenwood.common;
+package com.android.ravenwood;
 
 import android.system.ErrnoException;
 import android.system.StructStat;
 
+import com.android.ravenwood.common.JvmWorkaround;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
 import java.io.FileDescriptor;
 
 /**
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
index ed5a587..ba89f71 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -34,11 +34,11 @@
     }
 
     public boolean is64Bit() {
-        return true;
+        return "amd64".equals(System.getProperty("os.arch"));
     }
 
     public static boolean is64BitAbi(String abi) {
-        return true;
+        return abi.contains("64");
     }
 
     public Object newUnpaddedArray(Class<?> componentType, int minLength) {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java
index 65c285e..2bd1ae8 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java
@@ -16,7 +16,13 @@
 
 package libcore.io;
 
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.ravenwood.common.JvmWorkaround;
+
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.Socket;
 
@@ -47,6 +53,13 @@
         }
     }
 
+    public static void closeQuietly(FileDescriptor fd) {
+        try {
+            Os.close(fd);
+        } catch (ErrnoException ignored) {
+        }
+    }
+
     public static void deleteContents(File dir) throws IOException {
         File[] files = dir.listFiles();
         if (files != null) {
@@ -58,4 +71,17 @@
             }
         }
     }
+
+    /**
+     * FD owners currently unsupported under Ravenwood; ignored
+     */
+    public static void setFdOwner(FileDescriptor fd, Object owner) {
+    }
+
+    /**
+     * FD owners currently unsupported under Ravenwood; return FD directly
+     */
+    public static int acquireRawFd(FileDescriptor fd) {
+        return JvmWorkaround.getInstance().getFdInt(fd);
+    }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/FP16.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/FP16.java
new file mode 100644
index 0000000..478503b
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/FP16.java
@@ -0,0 +1,814 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.util;
+
+/**
+ * <p>The {@code FP16} class is a wrapper and a utility class to manipulate half-precision 16-bit
+ * <a href="https://en.wikipedia.org/wiki/Half-precision_floating-point_format">IEEE 754</a>
+ * floating point data types (also called fp16 or binary16). A half-precision float can be
+ * created from or converted to single-precision floats, and is stored in a short data type.
+ *
+ * <p>The IEEE 754 standard specifies an fp16 as having the following format:</p>
+ * <ul>
+ * <li>Sign bit: 1 bit</li>
+ * <li>Exponent width: 5 bits</li>
+ * <li>Significand: 10 bits</li>
+ * </ul>
+ *
+ * <p>The format is laid out as follows:</p>
+ * <pre>
+ * 1   11111   1111111111
+ * ^   --^--   -----^----
+ * sign  |          |_______ significand
+ *       |
+ *       -- exponent
+ * </pre>
+ *
+ * <p>Half-precision floating points can be useful to save memory and/or
+ * bandwidth at the expense of range and precision when compared to single-precision
+ * floating points (fp32).</p>
+ * <p>To help you decide whether fp16 is the right storage type for you need, please
+ * refer to the table below that shows the available precision throughout the range of
+ * possible values. The <em>precision</em> column indicates the step size between two
+ * consecutive numbers in a specific part of the range.</p>
+ *
+ * <table summary="Precision of fp16 across the range">
+ *     <tr><th>Range start</th><th>Precision</th></tr>
+ *     <tr><td>0</td><td>1 &frasl; 16,777,216</td></tr>
+ *     <tr><td>1 &frasl; 16,384</td><td>1 &frasl; 16,777,216</td></tr>
+ *     <tr><td>1 &frasl; 8,192</td><td>1 &frasl; 8,388,608</td></tr>
+ *     <tr><td>1 &frasl; 4,096</td><td>1 &frasl; 4,194,304</td></tr>
+ *     <tr><td>1 &frasl; 2,048</td><td>1 &frasl; 2,097,152</td></tr>
+ *     <tr><td>1 &frasl; 1,024</td><td>1 &frasl; 1,048,576</td></tr>
+ *     <tr><td>1 &frasl; 512</td><td>1 &frasl; 524,288</td></tr>
+ *     <tr><td>1 &frasl; 256</td><td>1 &frasl; 262,144</td></tr>
+ *     <tr><td>1 &frasl; 128</td><td>1 &frasl; 131,072</td></tr>
+ *     <tr><td>1 &frasl; 64</td><td>1 &frasl; 65,536</td></tr>
+ *     <tr><td>1 &frasl; 32</td><td>1 &frasl; 32,768</td></tr>
+ *     <tr><td>1 &frasl; 16</td><td>1 &frasl; 16,384</td></tr>
+ *     <tr><td>1 &frasl; 8</td><td>1 &frasl; 8,192</td></tr>
+ *     <tr><td>1 &frasl; 4</td><td>1 &frasl; 4,096</td></tr>
+ *     <tr><td>1 &frasl; 2</td><td>1 &frasl; 2,048</td></tr>
+ *     <tr><td>1</td><td>1 &frasl; 1,024</td></tr>
+ *     <tr><td>2</td><td>1 &frasl; 512</td></tr>
+ *     <tr><td>4</td><td>1 &frasl; 256</td></tr>
+ *     <tr><td>8</td><td>1 &frasl; 128</td></tr>
+ *     <tr><td>16</td><td>1 &frasl; 64</td></tr>
+ *     <tr><td>32</td><td>1 &frasl; 32</td></tr>
+ *     <tr><td>64</td><td>1 &frasl; 16</td></tr>
+ *     <tr><td>128</td><td>1 &frasl; 8</td></tr>
+ *     <tr><td>256</td><td>1 &frasl; 4</td></tr>
+ *     <tr><td>512</td><td>1 &frasl; 2</td></tr>
+ *     <tr><td>1,024</td><td>1</td></tr>
+ *     <tr><td>2,048</td><td>2</td></tr>
+ *     <tr><td>4,096</td><td>4</td></tr>
+ *     <tr><td>8,192</td><td>8</td></tr>
+ *     <tr><td>16,384</td><td>16</td></tr>
+ *     <tr><td>32,768</td><td>32</td></tr>
+ * </table>
+ *
+ * <p>This table shows that numbers higher than 1024 lose all fractional precision.</p>
+ *
+ * @hide
+ */
+
+public final class FP16 {
+    /**
+     * The number of bits used to represent a half-precision float value.
+     *
+     * @hide
+     */
+    public static final int SIZE = 16;
+
+    /**
+     * Epsilon is the difference between 1.0 and the next value representable
+     * by a half-precision floating-point.
+     *
+     * @hide
+     */
+    public static final short EPSILON = (short) 0x1400;
+
+    /**
+     * Maximum exponent a finite half-precision float may have.
+     *
+     * @hide
+     */
+    public static final int MAX_EXPONENT = 15;
+    /**
+     * Minimum exponent a normalized half-precision float may have.
+     *
+     * @hide
+     */
+    public static final int MIN_EXPONENT = -14;
+
+    /**
+     * Smallest negative value a half-precision float may have.
+     *
+     * @hide
+     */
+    public static final short LOWEST_VALUE = (short) 0xfbff;
+    /**
+     * Maximum positive finite value a half-precision float may have.
+     *
+     * @hide
+     */
+    public static final short MAX_VALUE = (short) 0x7bff;
+    /**
+     * Smallest positive normal value a half-precision float may have.
+     *
+     * @hide
+     */
+    public static final short MIN_NORMAL = (short) 0x0400;
+    /**
+     * Smallest positive non-zero value a half-precision float may have.
+     *
+     * @hide
+     */
+    public static final short MIN_VALUE = (short) 0x0001;
+    /**
+     * A Not-a-Number representation of a half-precision float.
+     *
+     * @hide
+     */
+    public static final short NaN = (short) 0x7e00;
+    /**
+     * Negative infinity of type half-precision float.
+     *
+     * @hide
+     */
+    public static final short NEGATIVE_INFINITY = (short) 0xfc00;
+    /**
+     * Negative 0 of type half-precision float.
+     *
+     * @hide
+     */
+    public static final short NEGATIVE_ZERO = (short) 0x8000;
+    /**
+     * Positive infinity of type half-precision float.
+     *
+     * @hide
+     */
+    public static final short POSITIVE_INFINITY = (short) 0x7c00;
+    /**
+     * Positive 0 of type half-precision float.
+     *
+     * @hide
+     */
+    public static final short POSITIVE_ZERO = (short) 0x0000;
+
+    /**
+     * The offset to shift by to obtain the sign bit.
+     *
+     * @hide
+     */
+    public static final int SIGN_SHIFT                = 15;
+
+    /**
+     * The offset to shift by to obtain the exponent bits.
+     *
+     * @hide
+     */
+    public static final int EXPONENT_SHIFT            = 10;
+
+    /**
+     * The bitmask to AND a number with to obtain the sign bit.
+     *
+     * @hide
+     */
+    public static final int SIGN_MASK                 = 0x8000;
+
+    /**
+     * The bitmask to AND a number shifted by {@link #EXPONENT_SHIFT} right, to obtain exponent bits.
+     *
+     * @hide
+     */
+    public static final int SHIFTED_EXPONENT_MASK     = 0x1f;
+
+    /**
+     * The bitmask to AND a number with to obtain significand bits.
+     *
+     * @hide
+     */
+    public static final int SIGNIFICAND_MASK          = 0x3ff;
+
+    /**
+     * The bitmask to AND with to obtain exponent and significand bits.
+     *
+     * @hide
+     */
+    public static final int EXPONENT_SIGNIFICAND_MASK = 0x7fff;
+
+    /**
+     * The offset of the exponent from the actual value.
+     *
+     * @hide
+     */
+    public static final int EXPONENT_BIAS             = 15;
+
+    private static final int FP32_SIGN_SHIFT            = 31;
+    private static final int FP32_EXPONENT_SHIFT        = 23;
+    private static final int FP32_SHIFTED_EXPONENT_MASK = 0xff;
+    private static final int FP32_SIGNIFICAND_MASK      = 0x7fffff;
+    private static final int FP32_EXPONENT_BIAS         = 127;
+    private static final int FP32_QNAN_MASK             = 0x400000;
+    private static final int FP32_DENORMAL_MAGIC = 126 << 23;
+    private static final float FP32_DENORMAL_FLOAT = Float.intBitsToFloat(FP32_DENORMAL_MAGIC);
+
+    /** Hidden constructor to prevent instantiation. */
+    private FP16() {}
+
+    /**
+     * <p>Compares the two specified half-precision float values. The following
+     * conditions apply during the comparison:</p>
+     *
+     * <ul>
+     * <li>{@link #NaN} is considered by this method to be equal to itself and greater
+     * than all other half-precision float values (including {@code #POSITIVE_INFINITY})</li>
+     * <li>{@link #POSITIVE_ZERO} is considered by this method to be greater than
+     * {@link #NEGATIVE_ZERO}.</li>
+     * </ul>
+     *
+     * @param x The first half-precision float value to compare.
+     * @param y The second half-precision float value to compare
+     *
+     * @return  The value {@code 0} if {@code x} is numerically equal to {@code y}, a
+     *          value less than {@code 0} if {@code x} is numerically less than {@code y},
+     *          and a value greater than {@code 0} if {@code x} is numerically greater
+     *          than {@code y}
+     *
+     * @hide
+     */
+    public static int compare(short x, short y) {
+        if (less(x, y)) return -1;
+        if (greater(x, y)) return 1;
+
+        // Collapse NaNs, akin to halfToIntBits(), but we want to keep
+        // (signed) short value types to preserve the ordering of -0.0
+        // and +0.0
+        short xBits = isNaN(x) ? NaN : x;
+        short yBits = isNaN(y) ? NaN : y;
+
+        return (xBits == yBits ? 0 : (xBits < yBits ? -1 : 1));
+    }
+
+    /**
+     * Returns the closest integral half-precision float value to the specified
+     * half-precision float value. Special values are handled in the
+     * following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The value of the specified half-precision float rounded to the nearest
+     *         half-precision float value
+     *
+     * @hide
+     */
+    public static short rint(short h) {
+        int bits = h & 0xffff;
+        int abs = bits & EXPONENT_SIGNIFICAND_MASK;
+        int result = bits;
+
+        if (abs < 0x3c00) {
+            result &= SIGN_MASK;
+            if (abs > 0x3800){
+                result |= 0x3c00;
+            }
+        } else if (abs < 0x6400) {
+            int exp = 25 - (abs >> 10);
+            int mask = (1 << exp) - 1;
+            result += ((1 << (exp - 1)) - (~(abs >> exp) & 1));
+            result &= ~mask;
+        }
+        if (isNaN((short) result)) {
+            // if result is NaN mask with qNaN
+            // (i.e. mask the most significant mantissa bit with 1)
+            // to comply with hardware implementations (ARM64, Intel, etc).
+            result |= NaN;
+        }
+
+        return (short) result;
+    }
+
+    /**
+     * Returns the smallest half-precision float value toward negative infinity
+     * greater than or equal to the specified half-precision float value.
+     * Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The smallest half-precision float value toward negative infinity
+     *         greater than or equal to the specified half-precision float value
+     *
+     * @hide
+     */
+    public static short ceil(short h) {
+        int bits = h & 0xffff;
+        int abs = bits & EXPONENT_SIGNIFICAND_MASK;
+        int result = bits;
+
+        if (abs < 0x3c00) {
+            result &= SIGN_MASK;
+            result |= 0x3c00 & -(~(bits >> 15) & (abs != 0 ? 1 : 0));
+        } else if (abs < 0x6400) {
+            abs = 25 - (abs >> 10);
+            int mask = (1 << abs) - 1;
+            result += mask & ((bits >> 15) - 1);
+            result &= ~mask;
+        }
+        if (isNaN((short) result)) {
+            // if result is NaN mask with qNaN
+            // (i.e. mask the most significant mantissa bit with 1)
+            // to comply with hardware implementations (ARM64, Intel, etc).
+            result |= NaN;
+        }
+
+        return (short) result;
+    }
+
+    /**
+     * Returns the largest half-precision float value toward positive infinity
+     * less than or equal to the specified half-precision float value.
+     * Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The largest half-precision float value toward positive infinity
+     *         less than or equal to the specified half-precision float value
+     *
+     * @hide
+     */
+    public static short floor(short h) {
+        int bits = h & 0xffff;
+        int abs = bits & EXPONENT_SIGNIFICAND_MASK;
+        int result = bits;
+
+        if (abs < 0x3c00) {
+            result &= SIGN_MASK;
+            result |= 0x3c00 & (bits > 0x8000 ? 0xffff : 0x0);
+        } else if (abs < 0x6400) {
+            abs = 25 - (abs >> 10);
+            int mask = (1 << abs) - 1;
+            result += mask & -(bits >> 15);
+            result &= ~mask;
+        }
+        if (isNaN((short) result)) {
+            // if result is NaN mask with qNaN
+            // i.e. (Mask the most significant mantissa bit with 1)
+            result |= NaN;
+        }
+
+        return (short) result;
+    }
+
+    /**
+     * Returns the truncated half-precision float value of the specified
+     * half-precision float value. Special values are handled in the following ways:
+     * <ul>
+     * <li>If the specified half-precision float is NaN, the result is NaN</li>
+     * <li>If the specified half-precision float is infinity (negative or positive),
+     * the result is infinity (with the same sign)</li>
+     * <li>If the specified half-precision float is zero (negative or positive),
+     * the result is zero (with the same sign)</li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return The truncated half-precision float value of the specified
+     *         half-precision float value
+     *
+     * @hide
+     */
+    public static short trunc(short h) {
+        int bits = h & 0xffff;
+        int abs = bits & EXPONENT_SIGNIFICAND_MASK;
+        int result = bits;
+
+        if (abs < 0x3c00) {
+            result &= SIGN_MASK;
+        } else if (abs < 0x6400) {
+            abs = 25 - (abs >> 10);
+            int mask = (1 << abs) - 1;
+            result &= ~mask;
+        }
+
+        return (short) result;
+    }
+
+    /**
+     * Returns the smaller of two half-precision float values (the value closest
+     * to negative infinity). Special values are handled in the following ways:
+     * <ul>
+     * <li>If either value is NaN, the result is NaN</li>
+     * <li>{@link #NEGATIVE_ZERO} is smaller than {@link #POSITIVE_ZERO}</li>
+     * </ul>
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     * @return The smaller of the two specified half-precision values
+     *
+     * @hide
+     */
+    public static short min(short x, short y) {
+        if (isNaN(x)) return NaN;
+        if (isNaN(y)) return NaN;
+
+        if ((x & EXPONENT_SIGNIFICAND_MASK) == 0 && (y & EXPONENT_SIGNIFICAND_MASK) == 0) {
+            return (x & SIGN_MASK) != 0 ? x : y;
+        }
+
+        return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <
+               ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y;
+    }
+
+    /**
+     * Returns the larger of two half-precision float values (the value closest
+     * to positive infinity). Special values are handled in the following ways:
+     * <ul>
+     * <li>If either value is NaN, the result is NaN</li>
+     * <li>{@link #POSITIVE_ZERO} is greater than {@link #NEGATIVE_ZERO}</li>
+     * </ul>
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return The larger of the two specified half-precision values
+     *
+     * @hide
+     */
+    public static short max(short x, short y) {
+        if (isNaN(x)) return NaN;
+        if (isNaN(y)) return NaN;
+
+        if ((x & EXPONENT_SIGNIFICAND_MASK) == 0 && (y & EXPONENT_SIGNIFICAND_MASK) == 0) {
+            return (x & SIGN_MASK) != 0 ? y : x;
+        }
+
+        return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >
+               ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y;
+    }
+
+    /**
+     * Returns true if the first half-precision float value is less (smaller
+     * toward negative infinity) than the second half-precision float value.
+     * If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is less than y, false otherwise
+     *
+     * @hide
+     */
+    public static boolean less(short x, short y) {
+        if (isNaN(x)) return false;
+        if (isNaN(y)) return false;
+
+        return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <
+               ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is less (smaller
+     * toward negative infinity) than or equal to the second half-precision
+     * float value. If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is less than or equal to y, false otherwise
+     *
+     * @hide
+     */
+    public static boolean lessEquals(short x, short y) {
+        if (isNaN(x)) return false;
+        if (isNaN(y)) return false;
+
+        return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <=
+               ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is greater (larger
+     * toward positive infinity) than the second half-precision float value.
+     * If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is greater than y, false otherwise
+     *
+     * @hide
+     */
+    public static boolean greater(short x, short y) {
+        if (isNaN(x)) return false;
+        if (isNaN(y)) return false;
+
+        return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >
+               ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+    }
+
+    /**
+     * Returns true if the first half-precision float value is greater (larger
+     * toward positive infinity) than or equal to the second half-precision float
+     * value. If either of the values is NaN, the result is false.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is greater than y, false otherwise
+     *
+     * @hide
+     */
+    public static boolean greaterEquals(short x, short y) {
+        if (isNaN(x)) return false;
+        if (isNaN(y)) return false;
+
+        return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >=
+               ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff);
+    }
+
+    /**
+     * Returns true if the two half-precision float values are equal.
+     * If either of the values is NaN, the result is false. {@link #POSITIVE_ZERO}
+     * and {@link #NEGATIVE_ZERO} are considered equal.
+     *
+     * @param x The first half-precision value
+     * @param y The second half-precision value
+     *
+     * @return True if x is equal to y, false otherwise
+     *
+     * @hide
+     */
+    public static boolean equals(short x, short y) {
+        if (isNaN(x)) return false;
+        if (isNaN(y)) return false;
+
+        return x == y || ((x | y) & EXPONENT_SIGNIFICAND_MASK) == 0;
+    }
+
+    /**
+     * Returns true if the specified half-precision float value represents
+     * infinity, false otherwise.
+     *
+     * @param h A half-precision float value
+     * @return True if the value is positive infinity or negative infinity,
+     *         false otherwise
+     *
+     * @hide
+     */
+    public static boolean isInfinite(short h) {
+        return (h & EXPONENT_SIGNIFICAND_MASK) == POSITIVE_INFINITY;
+    }
+
+    /**
+     * Returns true if the specified half-precision float value represents
+     * a Not-a-Number, false otherwise.
+     *
+     * @param h A half-precision float value
+     * @return True if the value is a NaN, false otherwise
+     *
+     * @hide
+     */
+    public static boolean isNaN(short h) {
+        return (h & EXPONENT_SIGNIFICAND_MASK) > POSITIVE_INFINITY;
+    }
+
+    /**
+     * Returns true if the specified half-precision float value is normalized
+     * (does not have a subnormal representation). If the specified value is
+     * {@link #POSITIVE_INFINITY}, {@link #NEGATIVE_INFINITY},
+     * {@link #POSITIVE_ZERO}, {@link #NEGATIVE_ZERO}, NaN or any subnormal
+     * number, this method returns false.
+     *
+     * @param h A half-precision float value
+     * @return True if the value is normalized, false otherwise
+     *
+     * @hide
+     */
+    public static boolean isNormalized(short h) {
+        return (h & POSITIVE_INFINITY) != 0 && (h & POSITIVE_INFINITY) != POSITIVE_INFINITY;
+    }
+
+    /**
+     * <p>Converts the specified half-precision float value into a
+     * single-precision float value. The following special cases are handled:</p>
+     * <ul>
+     * <li>If the input is {@link #NaN}, the returned value is {@link Float#NaN}</li>
+     * <li>If the input is {@link #POSITIVE_INFINITY} or
+     * {@link #NEGATIVE_INFINITY}, the returned value is respectively
+     * {@link Float#POSITIVE_INFINITY} or {@link Float#NEGATIVE_INFINITY}</li>
+     * <li>If the input is 0 (positive or negative), the returned value is +/-0.0f</li>
+     * <li>Otherwise, the returned value is a normalized single-precision float value</li>
+     * </ul>
+     *
+     * @param h The half-precision float value to convert to single-precision
+     * @return A normalized single-precision float value
+     *
+     * @hide
+     */
+    public static float toFloat(short h) {
+        int bits = h & 0xffff;
+        int s = bits & SIGN_MASK;
+        int e = (bits >>> EXPONENT_SHIFT) & SHIFTED_EXPONENT_MASK;
+        int m = (bits                        ) & SIGNIFICAND_MASK;
+
+        int outE = 0;
+        int outM = 0;
+
+        if (e == 0) { // Denormal or 0
+            if (m != 0) {
+                // Convert denorm fp16 into normalized fp32
+                float o = Float.intBitsToFloat(FP32_DENORMAL_MAGIC + m);
+                o -= FP32_DENORMAL_FLOAT;
+                return s == 0 ? o : -o;
+            }
+        } else {
+            outM = m << 13;
+            if (e == 0x1f) { // Infinite or NaN
+                outE = 0xff;
+                if (outM != 0) { // SNaNs are quieted
+                    outM |= FP32_QNAN_MASK;
+                }
+            } else {
+                outE = e - EXPONENT_BIAS + FP32_EXPONENT_BIAS;
+            }
+        }
+
+        int out = (s << 16) | (outE << FP32_EXPONENT_SHIFT) | outM;
+        return Float.intBitsToFloat(out);
+    }
+
+    /**
+     * <p>Converts the specified single-precision float value into a
+     * half-precision float value. The following special cases are handled:</p>
+     * <ul>
+     * <li>If the input is NaN (see {@link Float#isNaN(float)}), the returned
+     * value is {@link #NaN}</li>
+     * <li>If the input is {@link Float#POSITIVE_INFINITY} or
+     * {@link Float#NEGATIVE_INFINITY}, the returned value is respectively
+     * {@link #POSITIVE_INFINITY} or {@link #NEGATIVE_INFINITY}</li>
+     * <li>If the input is 0 (positive or negative), the returned value is
+     * {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+     * <li>If the input is a less than {@link #MIN_VALUE}, the returned value
+     * is flushed to {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li>
+     * <li>If the input is a less than {@link #MIN_NORMAL}, the returned value
+     * is a denorm half-precision float</li>
+     * <li>Otherwise, the returned value is rounded to the nearest
+     * representable half-precision float value</li>
+     * </ul>
+     *
+     * @param f The single-precision float value to convert to half-precision
+     * @return A half-precision float value
+     *
+     * @hide
+     */
+    public static short toHalf(float f) {
+        int bits = Float.floatToRawIntBits(f);
+        int s = (bits >>> FP32_SIGN_SHIFT    );
+        int e = (bits >>> FP32_EXPONENT_SHIFT) & FP32_SHIFTED_EXPONENT_MASK;
+        int m = (bits                        ) & FP32_SIGNIFICAND_MASK;
+
+        int outE = 0;
+        int outM = 0;
+
+        if (e == 0xff) { // Infinite or NaN
+            outE = 0x1f;
+            outM = m != 0 ? 0x200 : 0;
+        } else {
+            e = e - FP32_EXPONENT_BIAS + EXPONENT_BIAS;
+            if (e >= 0x1f) { // Overflow
+                outE = 0x1f;
+            } else if (e <= 0) { // Underflow
+                if (e < -10) {
+                    // The absolute fp32 value is less than MIN_VALUE, flush to +/-0
+                } else {
+                    // The fp32 value is a normalized float less than MIN_NORMAL,
+                    // we convert to a denorm fp16
+                    m = m | 0x800000;
+                    int shift = 14 - e;
+                    outM = m >> shift;
+
+                    int lowm = m & ((1 << shift) - 1);
+                    int hway = 1 << (shift - 1);
+                    // if above halfway or exactly halfway and outM is odd
+                    if (lowm + (outM & 1) > hway){
+                        // Round to nearest even
+                        // Can overflow into exponent bit, which surprisingly is OK.
+                        // This increment relies on the +outM in the return statement below
+                        outM++;
+                    }
+                }
+            } else {
+                outE = e;
+                outM = m >> 13;
+                // if above halfway or exactly halfway and outM is odd
+                if ((m & 0x1fff) + (outM & 0x1) > 0x1000) {
+                    // Round to nearest even
+                    // Can overflow into exponent bit, which surprisingly is OK.
+                    // This increment relies on the +outM in the return statement below
+                    outM++;
+                }
+            }
+        }
+        // The outM is added here as the +1 increments for outM above can
+        // cause an overflow in the exponent bit which is OK.
+        return (short) ((s << SIGN_SHIFT) | (outE << EXPONENT_SHIFT) + outM);
+    }
+
+    /**
+     * <p>Returns a hexadecimal string representation of the specified half-precision
+     * float value. If the value is a NaN, the result is <code>"NaN"</code>,
+     * otherwise the result follows this format:</p>
+     * <ul>
+     * <li>If the sign is positive, no sign character appears in the result</li>
+     * <li>If the sign is negative, the first character is <code>'-'</code></li>
+     * <li>If the value is inifinity, the string is <code>"Infinity"</code></li>
+     * <li>If the value is 0, the string is <code>"0x0.0p0"</code></li>
+     * <li>If the value has a normalized representation, the exponent and
+     * significand are represented in the string in two fields. The significand
+     * starts with <code>"0x1."</code> followed by its lowercase hexadecimal
+     * representation. Trailing zeroes are removed unless all digits are 0, then
+     * a single zero is used. The significand representation is followed by the
+     * exponent, represented by <code>"p"</code>, itself followed by a decimal
+     * string of the unbiased exponent</li>
+     * <li>If the value has a subnormal representation, the significand starts
+     * with <code>"0x0."</code> followed by its lowercase hexadecimal
+     * representation. Trailing zeroes are removed unless all digits are 0, then
+     * a single zero is used. The significand representation is followed by the
+     * exponent, represented by <code>"p-14"</code></li>
+     * </ul>
+     *
+     * @param h A half-precision float value
+     * @return A hexadecimal string representation of the specified value
+     *
+     * @hide
+     */
+    public static String toHexString(short h) {
+        StringBuilder o = new StringBuilder();
+
+        int bits = h & 0xffff;
+        int s = (bits >>> SIGN_SHIFT    );
+        int e = (bits >>> EXPONENT_SHIFT) & SHIFTED_EXPONENT_MASK;
+        int m = (bits                   ) & SIGNIFICAND_MASK;
+
+        if (e == 0x1f) { // Infinite or NaN
+            if (m == 0) {
+                if (s != 0) o.append('-');
+                o.append("Infinity");
+            } else {
+                o.append("NaN");
+            }
+        } else {
+            if (s == 1) o.append('-');
+            if (e == 0) {
+                if (m == 0) {
+                    o.append("0x0.0p0");
+                } else {
+                    o.append("0x0.");
+                    String significand = Integer.toHexString(m);
+                    o.append(significand.replaceFirst("0{2,}$", ""));
+                    o.append("p-14");
+                }
+            } else {
+                o.append("0x1.");
+                String significand = Integer.toHexString(m);
+                o.append(significand.replaceFirst("0{2,}$", ""));
+                o.append('p');
+                o.append(Integer.toString(e - EXPONENT_BIAS));
+            }
+        }
+
+        return o.toString();
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
index 14b5a4f..4e7dc5d 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/NativeAllocationRegistry.java
@@ -15,7 +15,7 @@
  */
 package libcore.util;
 
-import com.android.ravenwood.common.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeNative;
 
 import java.lang.ref.Cleaner;
 import java.lang.ref.Reference;
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index c804928..f5cb019f 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -245,7 +245,7 @@
     g_StructStat = findClass(env, "android/system/StructStat");
     g_StructTimespecClass = findClass(env, "android/system/StructTimespec");
 
-    jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/common/RavenwoodRuntimeNative",
+    jint res = jniRegisterNativeMethods(env, "com/android/ravenwood/RavenwoodRuntimeNative",
             sMethods, NELEM(sMethods));
     if (res < 0) {
         return res;
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/remove-ravenizer-output.sh b/ravenwood/scripts/remove-ravenizer-output.sh
new file mode 100755
index 0000000..be15b71
--- /dev/null
+++ b/ravenwood/scripts/remove-ravenizer-output.sh
@@ -0,0 +1,25 @@
+#!/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.
+
+# Delete all the ravenizer output jar files from Soong's intermediate directory.
+
+# `-a -prune` is needed because otherwise find would be confused if the directory disappears.
+
+find "${ANDROID_BUILD_TOP:?}/out/soong/.intermediates/" \
+    -type d \
+    -name 'ravenizer' \
+    -print \
+    -exec rm -fr \{\} \; \
+    -a -prune
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/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index 044239f..b3d3963 100644
--- a/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -16,6 +16,7 @@
 
 package com.android.ravenwoodtest.servicestest;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -62,7 +63,9 @@
         final SerialManager service = (SerialManager)
                 mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
         final String[] ports = service.getSerialPorts();
-        assertEquals(0, ports.length);
+        final String[] refPorts = mRavenwood.getContext().getResources().getStringArray(
+                com.android.internal.R.array.config_serialPorts);
+        assertArrayEquals(refPorts, ports);
     }
 
     @Test
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 5cffdec..9c86389 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -9,17 +9,13 @@
 com.android.internal.logging.testing.UiEventLoggerFake
 com.android.internal.os.AndroidPrintStream
 com.android.internal.os.BatteryStatsHistory
-com.android.internal.os.BatteryStatsHistory$TraceDelegate
-com.android.internal.os.BatteryStatsHistory$VarintParceler
 com.android.internal.os.BatteryStatsHistoryIterator
 com.android.internal.os.Clock
 com.android.internal.os.LongArrayMultiStateCounter
-com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer
 com.android.internal.os.LongMultiStateCounter
 com.android.internal.os.MonotonicClock
 com.android.internal.os.PowerProfile
 com.android.internal.os.PowerStats
-com.android.internal.os.PowerStats$Descriptor
 com.android.internal.os.RuntimeInit
 com.android.internal.power.EnergyConsumerStats
 com.android.internal.power.ModemPowerProfile
@@ -46,6 +42,7 @@
 android.util.EventLog
 android.util.FloatProperty
 android.util.FloatMath
+android.util.Half
 android.util.IndentingPrintWriter
 android.util.IntArray
 android.util.IntProperty
@@ -99,6 +96,7 @@
 android.util.SparseIntArray
 android.util.SparseLongArray
 android.util.SparseSetArray
+android.util.StateSet
 android.util.StringBuilderPrinter
 android.util.TeeWriter
 android.util.TimeUtils
@@ -121,15 +119,9 @@
 android.os.BaseBundle
 android.os.BatteryConsumer
 android.os.BatteryStats
-android.os.BatteryStats$HistoryItem
-android.os.BatteryStats$HistoryStepDetails
-android.os.BatteryStats$HistoryTag
-android.os.BatteryStats$LongCounter
-android.os.BatteryStats$ProcessStateChange
 android.os.BatteryUsageStats
 android.os.BatteryUsageStatsQuery
 android.os.Binder
-android.os.Binder$IdentitySupplier
 android.os.BluetoothBatteryStats
 android.os.Broadcaster
 android.os.Build
@@ -140,7 +132,6 @@
 android.os.DeadObjectException
 android.os.DeadSystemException
 android.os.FileUtils
-android.os.FileUtils$MemoryPipe
 android.os.Handler
 android.os.HandlerExecutor
 android.os.HandlerThread
@@ -152,8 +143,6 @@
 android.os.PackageTagsList
 android.os.Parcel
 android.os.ParcelFileDescriptor
-android.os.ParcelFileDescriptor$AutoCloseInputStream
-android.os.ParcelFileDescriptor$AutoCloseOutputStream
 android.os.ParcelFormatException
 android.os.ParcelUuid
 android.os.Parcelable
@@ -167,7 +156,6 @@
 android.os.RemoteException
 android.os.ResultReceiver
 android.os.ServiceManager
-android.os.ServiceManager$ServiceNotFoundException
 android.os.ServiceSpecificException
 android.os.StrictMode
 android.os.SystemClock
@@ -178,18 +166,14 @@
 android.os.Trace
 android.os.TransactionTooLargeException
 android.os.UserBatteryConsumer
-android.os.UserBatteryConsumer$Builder
 android.os.UidBatteryConsumer
-android.os.UidBatteryConsumer$Builder
 android.os.UserHandle
 android.os.UserManager
 android.os.VibrationAttributes
-android.os.VibrationAttributes$Builder
 android.os.WakeLockStats
 android.os.WorkSource
 
 android.content.ClipData
-android.content.ClipData$Item
 android.content.ClipDescription
 android.content.ClipboardManager
 android.content.ComponentName
@@ -206,11 +190,7 @@
 android.content.pm.ComponentInfo
 android.content.pm.PackageInfo
 android.content.pm.PackageItemInfo
-android.content.pm.PackageManager$Flags
-android.content.pm.PackageManager$PackageInfoFlags
-android.content.pm.PackageManager$ApplicationInfoFlags
-android.content.pm.PackageManager$ComponentInfoFlags
-android.content.pm.PackageManager$ResolveInfoFlags
+android.content.pm.PackageManager
 android.content.pm.PathPermission
 android.content.pm.ProviderInfo
 android.content.pm.ResolveInfo
@@ -221,10 +201,11 @@
 android.content.res.ApkAssets
 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
@@ -233,7 +214,6 @@
 android.content.res.FontScaleConverterImpl
 android.content.res.FontScaleConverterFactory
 android.content.res.Resources
-android.content.res.Resources$Theme
 android.content.res.ResourceId
 android.content.res.ResourcesImpl
 android.content.res.ResourcesKey
@@ -256,7 +236,6 @@
 android.database.DataSetObservable
 android.database.DataSetObserver
 android.database.MatrixCursor
-android.database.MatrixCursor$RowBuilder
 android.database.MergeCursor
 android.database.Observable
 android.database.SQLException
@@ -264,7 +243,6 @@
 android.database.sqlite.SQLiteException
 
 android.text.TextUtils
-android.text.TextUtils$SimpleStringSplitter
 
 android.accounts.Account
 
@@ -274,7 +252,11 @@
 android.graphics.Insets
 android.graphics.Interpolator
 android.graphics.Matrix
+android.graphics.Matrix44
+android.graphics.Outline
+android.graphics.ParcelableColorSpace
 android.graphics.Path
+android.graphics.PixelFormat
 android.graphics.Point
 android.graphics.PointF
 android.graphics.Rect
@@ -290,14 +272,11 @@
 android.app.Instrumentation
 android.app.LocaleConfig
 android.app.ResourcesManager
-android.app.ResourcesManager$UpdateHandler
 android.app.WindowConfiguration
 
 android.metrics.LogMaker
 
 android.view.Display
-android.view.Display$HdrCapabilities
-android.view.Display$Mode
 android.view.DisplayAdjustments
 android.view.DisplayInfo
 android.view.inputmethod.InputBinding
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 2d49128..d962c82 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -17,6 +17,13 @@
 rename com/.*/nano/   devicenano/
 rename android/.*/nano/   devicenano/
 
+# Support APIs not available in standard JRE
+class java.io.FileDescriptor keep
+    method getInt$ ()I @com.android.ravenwood.RavenwoodJdkPatch.getInt$
+    method setInt$ (I)V @com.android.ravenwood.RavenwoodJdkPatch.setInt$
+class java.util.LinkedHashMap keep
+    method eldest ()Ljava/util/Map$Entry; @com.android.ravenwood.RavenwoodJdkPatch.eldest
+
 # Exported to Mainline modules; cannot use annotations
 class com.android.internal.util.FastXmlSerializer keepclass
 class com.android.internal.util.FileRotator keepclass
diff --git a/ravenwood/texts/ravenwood-standard-options.txt b/ravenwood/texts/ravenwood-standard-options.txt
index f64f26d..952ab82 100644
--- a/ravenwood/texts/ravenwood-standard-options.txt
+++ b/ravenwood/texts/ravenwood-standard-options.txt
@@ -6,8 +6,6 @@
 --default-throw
 
 # Uncomment below lines to enable each feature.
-# --enable-non-stub-method-check
---no-non-stub-method-check
 
 #--default-method-call-hook
 #    com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt
new file mode 100644
index 0000000..0dcd271
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.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.
+ */
+@file:Suppress("ktlint:standard:filename")
+
+package com.android.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.UserErrorException
+
+/**
+ * Use it for internal exception that really shouldn't happen.
+ */
+class RavenizerInternalException(message: String) : Exception(message)
+
+/**
+ * Thrown when an invalid test is detected in the target jar. (e.g. JUni3 tests)
+ */
+class RavenizerInvalidTestException(message: String) : Exception(message), UserErrorException
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..a38512e 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
@@ -44,6 +44,9 @@
     /** Time took to build [ClasNodes] */
     var loadStructureTime: Double = .0,
 
+    /** Time took to validate the classes */
+    var validationTime: Double = .0,
+
     /** Total real time spent for converting the jar file */
     var totalProcessTime: Double = .0,
 
@@ -67,6 +70,7 @@
             RavenizerStats{
               totalTime=$totalTime,
               loadStructureTime=$loadStructureTime,
+              validationTime=$validationTime,
               totalProcessTime=$totalProcessTime,
               totalConversionTime=$totalConversionTime,
               totalCopyTime=$totalCopyTime,
@@ -84,16 +88,44 @@
 class Ravenizer(val options: RavenizerOptions) {
     fun run() {
         val stats = RavenizerStats()
+
+        val fatalValidation = options.fatalValidation.get
+
         stats.totalTime = log.nTime {
-            process(options.inJar.get, options.outJar.get, stats)
+            process(
+                options.inJar.get,
+                options.outJar.get,
+                options.enableValidation.get,
+                fatalValidation,
+                stats,
+            )
         }
         log.i(stats.toString())
     }
 
-    private fun process(inJar: String, outJar: String, stats: RavenizerStats) {
+    private fun process(
+        inJar: String,
+        outJar: String,
+        enableValidation: Boolean,
+        fatalValidation: Boolean,
+        stats: RavenizerStats,
+    ) {
         var allClasses = ClassNodes.loadClassStructures(inJar) {
             time -> stats.loadStructureTime = time
         }
+        if (enableValidation) {
+            stats.validationTime = log.iTime("Validating classes") {
+                if (!validateClasses(allClasses)) {
+                    var message = "Invalid test class(es) detected." +
+                            " See error log for details."
+                    if (fatalValidation) {
+                        throw RavenizerInvalidTestException(message)
+                    } else {
+                        log.w("Warning: $message")
+                    }
+                }
+            }
+        }
 
         stats.totalProcessTime = log.iTime("$executableName processing $inJar") {
             ZipFile(inJar).use { inZip ->
@@ -177,7 +209,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 +224,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 +237,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/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index e85e3be..e8341e5 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -27,6 +27,12 @@
 
     /** Output jar file */
     var outJar: SetOnce<String> = SetOnce(""),
+
+    /** Whether to enable test validation. */
+    var enableValidation: SetOnce<Boolean> = SetOnce(true),
+
+    /** Whether the validation failure is fatal or not. */
+    var fatalValidation: SetOnce<Boolean> = SetOnce(false),
 ) {
     companion object {
         fun parseArgs(args: Array<String>): RavenizerOptions {
@@ -52,6 +58,12 @@
                         "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
                         "--out-jar" -> ret.outJar.set(nextArg())
 
+                        "--enable-validation" -> ret.enableValidation.set(true)
+                        "--disable-validation" -> ret.enableValidation.set(false)
+
+                        "--fatal-validation" -> ret.fatalValidation.set(true)
+                        "--no-fatal-validation" -> ret.fatalValidation.set(false)
+
                         else -> throw ArgumentsException("Unknown option: $arg")
                     }
                 } catch (e: SetOnce.SetMoreThanOnceException) {
@@ -74,6 +86,8 @@
             RavenizerOptions{
               inJar=$inJar,
               outJar=$outJar,
+              enableValidation=$enableValidation,
+              fatalValidation=$fatalValidation,
             }
             """.trimIndent()
     }
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..1aa70c08 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,35 @@
  */
 package com.android.platform.test.ravenwood.ravenizer
 
+import android.platform.test.annotations.NoRavenizer
+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 com.android.hoststubgen.asm.toHumanReadableClassName
+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 humanReadableName = type.internalName.toHumanReadableClassName()
+}
 
-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 noRavenizerAnotType = TypeHolder(NoRavenizer::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 +56,47 @@
 
     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/",
+        "junit/",
+        "org/junit/",
+        "org/mockito/",
+        "kotlin/",
+        "androidx/",
+        "android/support/",
+        // TODO -- anything else?
+    )
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
new file mode 100644
index 0000000..27092d2
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
@@ -0,0 +1,81 @@
+/*
+ * 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
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.startsWithAny
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.log
+import org.objectweb.asm.tree.ClassNode
+
+fun validateClasses(classes: ClassNodes): Boolean {
+    var allOk = true
+    classes.forEach { allOk = checkClass(it, classes) && allOk }
+
+    return allOk
+}
+
+/**
+ * Validate a class.
+ *
+ * - A test class shouldn't extend
+ *
+ */
+fun checkClass(cn: ClassNode, classes: ClassNodes): Boolean {
+    if (cn.name.shouldByBypassed()) {
+        // Class doesn't need to be checked.
+        return true
+    }
+    var allOk = true
+
+    // See if there's any class that extends a legacy base class.
+    // But ignore the base classes in android.test.
+    if (!cn.name.startsWithAny("android/test/")) {
+        allOk = checkSuperClass(cn, cn, classes) && allOk
+    }
+    return allOk
+}
+
+fun checkSuperClass(targetClass: ClassNode, currentClass: ClassNode, classes: ClassNodes): Boolean {
+    if (currentClass.superName == null || currentClass.superName == "java/lang/Object") {
+        return true // No parent class
+    }
+    if (currentClass.superName.isLegacyTestBaseClass()) {
+        log.e("Error: Class ${targetClass.name.toHumanReadableClassName()} extends"
+                + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}.")
+        return false
+    }
+    classes.findClass(currentClass.superName)?.let {
+        return checkSuperClass(targetClass, it, classes)
+    }
+    // Super class not found.
+    // log.w("Class ${currentClass.superName} not found.")
+    return true
+}
+
+/**
+ * Check if a class internal name is a known legacy test base class.
+ */
+fun String.isLegacyTestBaseClass(): Boolean {
+    return this.startsWithAny(
+        "junit/framework/TestCase",
+
+        // In case the test doesn't statically include JUnit, we need
+        "android/test/AndroidTestCase",
+        "android/test/InstrumentationTestCase",
+        "android/test/InstrumentationTestSuite",
+    )
+}
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..eaef2cf
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
@@ -0,0 +1,466 @@
+/*
+ * 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.noRavenizerAnotType
+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.i("Update the @RunWith: ${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 {
+            if (!isTestLookingClass(classes, className)) {
+                return false
+            }
+            // Don't process a class if it has a @NoRavenizer annotation.
+            classes.findClass(className)?.let { cn ->
+                if (cn.findAnyAnnotation(noRavenizerAnotType.descAsSet) != null) {
+                    log.w("Class ${className.toHumanReadableClassName()} has" +
+                        " @${noRavenizerAnotType.humanReadableName}. Skipping."
+                    )
+                    return false
+                }
+            }
+            return true
+        }
+
+        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..ffa4841 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -117,7 +117,14 @@
     name: "enable_magnification_follows_mouse"
     namespace: "accessibility"
     description: "Whether to enable mouse following for fullscreen magnification"
-    bug: "335494097"
+    bug: "354696546"
+}
+
+flag {
+    name: "enable_magnification_keyboard_control"
+    namespace: "accessibility"
+    description: "Whether to enable keyboard control for magnification"
+    bug: "355487062"
 }
 
 flag {
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index edb6390..3224b27 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -34,6 +34,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
+import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled;
 import static com.android.window.flags.Flags.deleteCaptureDisplay;
 
 import android.accessibilityservice.AccessibilityGestureEvent;
@@ -1100,11 +1101,14 @@
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("performGlobalAction", "action=" + action);
         }
+        int currentUserId;
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return false;
             }
+            currentUserId = mSystemSupport.getCurrentUserIdLocked();
         }
+        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
         final long identity = Binder.clearCallingIdentity();
         try {
             return mSystemActionPerformer.performSystemAction(action);
@@ -2750,6 +2754,11 @@
     @RequiresNoPermission
     @Override
     public void setAnimationScale(float scale) {
+        int currentUserId;
+        synchronized (mLock) {
+            currentUserId = mSystemSupport.getCurrentUserIdLocked();
+        }
+        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
         final long identity = Binder.clearCallingIdentity();
         try {
             Settings.Global.putFloat(
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index d16a665..1b2447e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -26,6 +26,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Region;
+import android.hardware.input.InputManager;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -57,6 +58,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.StringJoiner;
 
 /**
@@ -748,6 +750,7 @@
 
         if ((mEnabledFeatures & FLAG_FEATURE_MOUSE_KEYS) != 0) {
             mMouseKeysInterceptor = new MouseKeysInterceptor(mAms,
+                    Objects.requireNonNull(mContext.getSystemService(InputManager.class)),
                     Looper.myLooper(),
                     Display.DEFAULT_DISPLAY);
             addFirstEventHandler(Display.DEFAULT_DISPLAY, mMouseKeysInterceptor);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0aa750e..b541345 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -63,6 +63,7 @@
 import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled;
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 import android.accessibilityservice.AccessibilityGestureEvent;
@@ -162,6 +163,7 @@
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
 import android.view.accessibility.IMagnificationConnection;
+import android.view.accessibility.IUserInitializationCompleteCallback;
 import android.view.inputmethod.EditorInfo;
 
 import com.android.internal.R;
@@ -309,6 +311,8 @@
 
     private final PowerManager mPowerManager;
 
+    private final UserManager mUserManager;
+
     private final WindowManagerInternal mWindowManagerService;
 
     private final AccessibilitySecurityPolicy mSecurityPolicy;
@@ -363,6 +367,11 @@
     private final List<SendWindowStateChangedEventRunnable> mSendWindowStateChangedEventRunnables =
             new ArrayList<>();
 
+    @VisibleForTesting
+    final HashSet<IUserInitializationCompleteCallback>
+            mUserInitializationCompleteCallbacks =
+            new HashSet<IUserInitializationCompleteCallback>();
+
     @GuardedBy("mLock")
     private @UserIdInt int mCurrentUserId = UserHandle.USER_SYSTEM;
 
@@ -507,6 +516,7 @@
         super(permissionEnforcer);
         mContext = context;
         mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mUserManager = mContext.getSystemService(UserManager.class);
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
         mTraceManager = AccessibilityTraceManager.getInstance(
                 mWindowManagerService.getAccessibilityController(), this, mLock);
@@ -542,6 +552,7 @@
         super(PermissionEnforcer.fromContext(context));
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
+        mUserManager = context.getSystemService(UserManager.class);
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
         mTraceManager = AccessibilityTraceManager.getInstance(
                 mWindowManagerService.getAccessibilityController(), this, mLock);
@@ -1263,6 +1274,11 @@
     @EnforcePermission(MANAGE_ACCESSIBILITY)
     public void registerSystemAction(RemoteAction action, int actionId) {
         registerSystemAction_enforcePermission();
+        int currentUserId;
+        synchronized (mLock) {
+            currentUserId = mCurrentUserId;
+        }
+        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".registerSystemAction",
                     FLAGS_ACCESSIBILITY_MANAGER, "action=" + action + ";actionId=" + actionId);
@@ -1279,6 +1295,11 @@
     @EnforcePermission(MANAGE_ACCESSIBILITY)
     public void unregisterSystemAction(int actionId) {
         unregisterSystemAction_enforcePermission();
+        int currentUserId;
+        synchronized (mLock) {
+            currentUserId = mCurrentUserId;
+        }
+        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction",
                     FLAGS_ACCESSIBILITY_MANAGER, "actionId=" + actionId);
@@ -1606,6 +1627,11 @@
     @EnforcePermission(STATUS_BAR_SERVICE)
     public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
         notifyAccessibilityButtonClicked_enforcePermission();
+        int currentUserId;
+        synchronized (mLock) {
+            currentUserId = mCurrentUserId;
+        }
+        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked",
                     FLAGS_ACCESSIBILITY_MANAGER,
@@ -1634,6 +1660,11 @@
     @EnforcePermission(STATUS_BAR_SERVICE)
     public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
         notifyAccessibilityButtonVisibilityChanged_enforcePermission();
+        int currentUserId;
+        synchronized (mLock) {
+            currentUserId = mCurrentUserId;
+        }
+        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged",
                     FLAGS_ACCESSIBILITY_MANAGER, "shown=" + shown);
@@ -1974,9 +2005,8 @@
                         this, 0, oldUserState.mUserId));
             }
 
-            // Announce user changes only if more that one exist.
-            UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            final boolean announceNewUser = userManager.getUsers().size() > 1;
+            // Announce user changes only if more than one exist.
+            final boolean announceNewUser = mUserManager.getUsers().size() > 1;
 
             // The user changed.
             mCurrentUserId = userId;
@@ -2010,6 +2040,17 @@
                         obtainMessage(AccessibilityManagerService::announceNewUserIfNeeded, this),
                         WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS);
             }
+
+            for (IUserInitializationCompleteCallback callback
+                    : mUserInitializationCompleteCallbacks) {
+                try {
+                    callback.onUserInitializationComplete(mCurrentUserId);
+                } catch (RemoteException re) {
+                    Log.e("AccessibilityManagerService",
+                            "Error while dispatching userInitializationComplete callback: ",
+                            re);
+                }
+            }
         }
     }
 
@@ -2017,10 +2058,8 @@
         synchronized (mLock) {
             AccessibilityUserState userState = getCurrentUserStateLocked();
             if (userState.isHandlingAccessibilityEventsLocked()) {
-                UserManager userManager = (UserManager) mContext.getSystemService(
-                        Context.USER_SERVICE);
                 String message = mContext.getString(R.string.user_switched,
-                        userManager.getUserInfo(mCurrentUserId).name);
+                        mUserManager.getUserInfo(mCurrentUserId).name);
                 AccessibilityEvent event = AccessibilityEvent.obtain(
                         AccessibilityEvent.TYPE_ANNOUNCEMENT);
                 event.getText().add(message);
@@ -3185,6 +3224,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void updateWindowsForAccessibilityCallbackLocked(AccessibilityUserState userState) {
         // We observe windows for accessibility only if there is at least
         // one bound service that can retrieve window content that specified
@@ -3211,6 +3251,14 @@
         for (int i = 0; i < displays.size(); i++) {
             final Display display = displays.get(i);
             if (display != null) {
+                // When supporting visible background users, only track windows on the display
+                // assigned to the current user. The proxy displays are registered only to the
+                // current user.
+                if (UserManager.isVisibleBackgroundUsersEnabled()
+                        && !mProxyManager.isProxyedDisplay(display.getDisplayId())
+                        && !mUmi.isUserVisible(mCurrentUserId, display.getDisplayId())) {
+                    continue;
+                }
                 if (observingWindows) {
                     mA11yWindowManager.startTrackingWindows(display.getDisplayId(),
                             mProxyManager.isProxyedDisplay(display.getDisplayId()));
@@ -4799,6 +4847,11 @@
             throws RemoteException {
         registerProxyForDisplay_enforcePermission();
         mSecurityPolicy.checkForAccessibilityPermissionOrRole();
+        int currentUserId;
+        synchronized (mLock) {
+            currentUserId = mCurrentUserId;
+        }
+        enforceCurrentUserIfVisibleBackgroundEnabled(currentUserId);
         if (client == null) {
             return false;
         }
@@ -4850,40 +4903,26 @@
     }
 
     @Override
-    @RequiresNoPermission
-    public boolean startFlashNotificationSequence(String opPkg,
-            @FlashNotificationReason int reason, IBinder token) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return mFlashNotificationsController.startFlashNotificationSequence(opPkg,
-                    reason, token);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+    @EnforcePermission(MANAGE_ACCESSIBILITY)
+    public boolean startFlashNotificationSequence(String opPkg, @FlashNotificationReason int reason,
+            IBinder token) {
+        startFlashNotificationSequence_enforcePermission();
+        return mFlashNotificationsController.startFlashNotificationSequence(opPkg, reason, token);
     }
 
     @Override
-    @RequiresNoPermission
+    @EnforcePermission(MANAGE_ACCESSIBILITY)
     public boolean stopFlashNotificationSequence(String opPkg) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return mFlashNotificationsController.stopFlashNotificationSequence(opPkg);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        stopFlashNotificationSequence_enforcePermission();
+        return mFlashNotificationsController.stopFlashNotificationSequence(opPkg);
     }
 
     @Override
-    @RequiresNoPermission
-    public boolean startFlashNotificationEvent(String opPkg,
-            @FlashNotificationReason int reason, String reasonPkg) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return mFlashNotificationsController.startFlashNotificationEvent(opPkg,
-                    reason, reasonPkg);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+    @EnforcePermission(MANAGE_ACCESSIBILITY)
+    public boolean startFlashNotificationEvent(String opPkg, @FlashNotificationReason int reason,
+            String reasonPkg) {
+        startFlashNotificationEvent_enforcePermission();
+        return mFlashNotificationsController.startFlashNotificationEvent(opPkg, reason, reasonPkg);
     }
 
     @Override
@@ -6215,6 +6254,24 @@
     }
 
     @Override
+    @RequiresNoPermission
+    public void registerUserInitializationCompleteCallback(
+            IUserInitializationCompleteCallback callback) {
+        synchronized (mLock) {
+            mUserInitializationCompleteCallbacks.add(callback);
+        }
+    }
+
+    @Override
+    @RequiresNoPermission
+    public void unregisterUserInitializationCompleteCallback(
+            IUserInitializationCompleteCallback callback) {
+        synchronized (mLock) {
+            mUserInitializationCompleteCallbacks.remove(callback);
+        }
+    }
+
+    @Override
     @EnforcePermission(INJECT_EVENTS)
     public void injectInputEventToInputFilter(InputEvent event) {
         injectInputEventToInputFilter_enforcePermission();
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 6007bfd..9a81aa6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -594,10 +594,6 @@
 
         private boolean windowMattersToAccessibilityLocked(AccessibilityWindow a11yWindow,
                 int windowId, Region regionInScreen, Region unaccountedSpace) {
-            if (a11yWindow.ignoreRecentsAnimationForAccessibility()) {
-                return false;
-            }
-
             if (a11yWindow.isFocused()) {
                 return true;
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
index 2ce5c2b..54368ca 100644
--- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java
@@ -23,6 +23,7 @@
 import android.annotation.RequiresPermission;
 import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
+import android.hardware.input.InputManager;
 import android.hardware.input.VirtualMouse;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseConfig;
@@ -34,8 +35,11 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.InputDevice;
 import android.view.KeyEvent;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 
@@ -60,7 +64,7 @@
  * mouse keys of each physical keyboard will control a single (global) mouse pointer.
  */
 public class MouseKeysInterceptor extends BaseEventStreamTransformation
-        implements Handler.Callback {
+        implements Handler.Callback, InputManager.InputDeviceListener {
     private static final String LOG_TAG = "MouseKeysInterceptor";
 
     // To enable these logs, run: 'adb shell setprop log.tag.MouseKeysInterceptor DEBUG'
@@ -77,10 +81,19 @@
 
     private final AccessibilityManagerService mAms;
     private final Handler mHandler;
+    private final InputManager mInputManager;
 
     /** Thread to wait for virtual mouse creation to complete */
     private final Thread mCreateVirtualMouseThread;
 
+    /**
+     * Map of device IDs to a map of key codes to their corresponding {@link MouseKeyEvent} values.
+     * To ensure thread safety for the map, all access and modification of the map
+     * should happen on the same thread, i.e., on the handler thread.
+     */
+    private final SparseArray<SparseArray<MouseKeyEvent>> mDeviceKeyCodeMap =
+            new SparseArray<>();
+
     VirtualDeviceManager.VirtualDevice mVirtualDevice = null;
 
     private VirtualMouse mVirtualMouse = null;
@@ -102,6 +115,21 @@
     /** Whether scroll toggle is on */
     private boolean mScrollToggleOn = false;
 
+    /** The ID of the input device that is currently active */
+    private int mActiveInputDeviceId = 0;
+
+    /**
+     * Enum representing different types of mouse key events, each associated with a specific
+     * key code.
+     *
+     * <p> These events correspond to various mouse actions such as directional movements,
+     * clicks, and scrolls, mapped to specific keys on the keyboard.
+     * The key codes here are the QWERTY key codes, and should be accessed via
+     * {@link MouseKeyEvent#getKeyCode(InputDevice)}
+     * so that it is mapped to the equivalent key on the keyboard layout of the keyboard device
+     * that is actually in use.
+     * </p>
+     */
     public enum MouseKeyEvent {
         DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_7),
         UP_MOVE_OR_SCROLL(KeyEvent.KEYCODE_8),
@@ -117,34 +145,64 @@
         RELEASE(KeyEvent.KEYCODE_COMMA),
         SCROLL_TOGGLE(KeyEvent.KEYCODE_PERIOD);
 
-        private final int mKeyCode;
+        private final int mLocationKeyCode;
         MouseKeyEvent(int enumValue) {
-            mKeyCode = enumValue;
+            mLocationKeyCode = enumValue;
         }
 
-        private static final SparseArray<MouseKeyEvent> VALUE_TO_ENUM_MAP = new SparseArray<>();
-
-        static {
-            for (MouseKeyEvent type : MouseKeyEvent.values()) {
-                VALUE_TO_ENUM_MAP.put(type.mKeyCode, type);
-            }
-        }
-
+        @VisibleForTesting
         public final int getKeyCodeValue() {
-            return mKeyCode;
+            return mLocationKeyCode;
         }
 
         /**
-         * Convert int value of the key code to corresponding MouseEvent enum. If no matching
-         * value is found, this will return {@code null}.
+         * Get the key code associated with the given MouseKeyEvent for the given keyboard
+         * input device, taking into account its layout.
+         * The default is to return the keycode for the default layout (QWERTY).
+         * We check if the input device has been generated using {@link InputDevice#getGeneration()}
+         * to test with the default {@link MouseKeyEvent} values in the unit tests.
+         */
+        public int getKeyCode(InputDevice inputDevice) {
+            if (inputDevice.getGeneration() == -1) {
+                return mLocationKeyCode;
+            }
+            return inputDevice.getKeyCodeForKeyLocation(mLocationKeyCode);
+        }
+
+        /**
+         * Convert int value of the key code to corresponding {@link MouseKeyEvent}
+         * enum for a particular device ID.
+         * If no matching value is found, this will return {@code null}.
          */
         @Nullable
-        public static MouseKeyEvent from(int value) {
-            return VALUE_TO_ENUM_MAP.get(value);
+        public static MouseKeyEvent from(int keyCode, int deviceId,
+                SparseArray<SparseArray<MouseKeyEvent>> deviceKeyCodeMap) {
+            SparseArray<MouseKeyEvent> keyCodeToEnumMap = deviceKeyCodeMap.get(deviceId);
+            if (keyCodeToEnumMap != null) {
+                return keyCodeToEnumMap.get(keyCode);
+            }
+            return null;
         }
     }
 
     /**
+     * Create a map of key codes to their corresponding {@link MouseKeyEvent} values
+     * for a specific input device.
+     * The key for {@code mDeviceKeyCodeMap} is the deviceId.
+     * The key for {@code keyCodeToEnumMap} is the keycode for each
+     * {@link MouseKeyEvent} according to the keyboard layout of the input device.
+     */
+    public void initializeDeviceToEnumMap(InputDevice inputDevice) {
+        int deviceId = inputDevice.getId();
+        SparseArray<MouseKeyEvent> keyCodeToEnumMap = new SparseArray<>();
+        for (MouseKeyEvent mouseKeyEventType : MouseKeyEvent.values()) {
+            int keyCode = mouseKeyEventType.getKeyCode(inputDevice);
+            keyCodeToEnumMap.put(keyCode, mouseKeyEventType);
+        }
+        mDeviceKeyCodeMap.put(deviceId, keyCodeToEnumMap);
+    }
+
+    /**
      * Construct a new MouseKeysInterceptor.
      *
      * @param service The service to notify of key events
@@ -152,8 +210,10 @@
      * @param displayId Display ID to send mouse events to
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
-    public MouseKeysInterceptor(AccessibilityManagerService service, Looper looper, int displayId) {
+    public MouseKeysInterceptor(AccessibilityManagerService service,
+            InputManager inputManager, Looper looper, int displayId) {
         mAms = service;
+        mInputManager = inputManager;
         mHandler = new Handler(looper, this);
         // Create the virtual mouse on a separate thread since virtual device creation
         // should happen on an auxiliary thread, and not from the handler's thread.
@@ -163,6 +223,9 @@
             mVirtualMouse = createVirtualMouse(displayId);
         });
         mCreateVirtualMouseThread.start();
+        // Register an input device listener to watch when input devices are
+        // added, removed or reconfigured.
+        mInputManager.registerInputDeviceListener(this, mHandler);
     }
 
     /**
@@ -215,7 +278,8 @@
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     private void performMouseScrollAction(int keyCode) {
-        MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+        MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(
+                keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap);
         float y = switch (mouseKeyEvent) {
             case UP_MOVE_OR_SCROLL -> 1.0f;
             case DOWN_MOVE_OR_SCROLL -> -1.0f;
@@ -247,15 +311,18 @@
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     private void performMouseButtonAction(int keyCode) {
-        MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+        MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(
+                keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap);
         int buttonCode = switch (mouseKeyEvent) {
             case LEFT_CLICK -> VirtualMouseButtonEvent.BUTTON_PRIMARY;
             case RIGHT_CLICK -> VirtualMouseButtonEvent.BUTTON_SECONDARY;
             default -> VirtualMouseButtonEvent.BUTTON_UNKNOWN;
         };
         if (buttonCode != VirtualMouseButtonEvent.BUTTON_UNKNOWN) {
-            sendVirtualMouseButtonEvent(buttonCode, VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
-            sendVirtualMouseButtonEvent(buttonCode, VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
+            sendVirtualMouseButtonEvent(buttonCode,
+                    VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
+            sendVirtualMouseButtonEvent(buttonCode,
+                    VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE);
         }
         if (DEBUG) {
             if (buttonCode == VirtualMouseButtonEvent.BUTTON_UNKNOWN) {
@@ -293,7 +360,9 @@
     private void performMousePointerAction(int keyCode) {
         float x = 0f;
         float y = 0f;
-        MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(keyCode);
+        MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from(
+                keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap);
+
         switch (mouseKeyEvent) {
             case DIAGONAL_DOWN_LEFT_MOVE -> {
                 x = -MOUSE_POINTER_MOVEMENT_STEP / sqrt(2);
@@ -339,18 +408,19 @@
         }
     }
 
-    private boolean isMouseKey(int keyCode) {
-        return MouseKeyEvent.VALUE_TO_ENUM_MAP.contains(keyCode);
+    private boolean isMouseKey(int keyCode, int deviceId) {
+        SparseArray<MouseKeyEvent> keyCodeToEnumMap = mDeviceKeyCodeMap.get(deviceId);
+        return keyCodeToEnumMap.contains(keyCode);
     }
 
-    private boolean isMouseButtonKey(int keyCode) {
-        return keyCode == MouseKeyEvent.LEFT_CLICK.getKeyCodeValue()
-                || keyCode == MouseKeyEvent.RIGHT_CLICK.getKeyCodeValue();
+    private boolean isMouseButtonKey(int keyCode, InputDevice inputDevice) {
+        return keyCode == MouseKeyEvent.LEFT_CLICK.getKeyCode(inputDevice)
+                || keyCode == MouseKeyEvent.RIGHT_CLICK.getKeyCode(inputDevice);
     }
 
-    private boolean isMouseScrollKey(int keyCode) {
-        return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCodeValue()
-                || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCodeValue();
+    private boolean isMouseScrollKey(int keyCode, InputDevice inputDevice) {
+        return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCode(inputDevice)
+                || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCode(inputDevice);
     }
 
     /**
@@ -373,7 +443,7 @@
     }
 
     /**
-     * Handles key events and forwards mouse key events to the virtual mouse.
+     * Handles key events and forwards mouse key events to the virtual mouse on the handler thread.
      *
      * @param event The key event to handle.
      * @param policyFlags The policy flags associated with the key event.
@@ -385,31 +455,45 @@
             mAms.getTraceManager().logTrace(LOG_TAG + ".onKeyEvent",
                     FLAGS_INPUT_FILTER, "event=" + event + ";policyFlags=" + policyFlags);
         }
+
+        mHandler.post(() -> {
+            onKeyEventInternal(event, policyFlags);
+        });
+    }
+
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    private void onKeyEventInternal(KeyEvent event, int policyFlags) {
         boolean isDown = event.getAction() == KeyEvent.ACTION_DOWN;
         int keyCode = event.getKeyCode();
+        mActiveInputDeviceId = event.getDeviceId();
+        InputDevice inputDevice = mInputManager.getInputDevice(mActiveInputDeviceId);
 
-        if (!isMouseKey(keyCode)) {
+        if (!mDeviceKeyCodeMap.contains(mActiveInputDeviceId)) {
+            initializeDeviceToEnumMap(inputDevice);
+        }
+
+        if (!isMouseKey(keyCode, mActiveInputDeviceId)) {
             // Pass non-mouse key events to the next handler
             super.onKeyEvent(event, policyFlags);
         } else if (isDown) {
-            if (keyCode == MouseKeyEvent.SCROLL_TOGGLE.getKeyCodeValue()) {
+            if (keyCode == MouseKeyEvent.SCROLL_TOGGLE.getKeyCode(inputDevice)) {
                 mScrollToggleOn = !mScrollToggleOn;
                 if (DEBUG) {
                     Slog.d(LOG_TAG, "Scroll toggle " + (mScrollToggleOn ? "ON" : "OFF"));
                 }
-            } else if (keyCode == MouseKeyEvent.HOLD.getKeyCodeValue()) {
+            } else if (keyCode == MouseKeyEvent.HOLD.getKeyCode(inputDevice)) {
                 sendVirtualMouseButtonEvent(
                         VirtualMouseButtonEvent.BUTTON_PRIMARY,
                         VirtualMouseButtonEvent.ACTION_BUTTON_PRESS
                 );
-            } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCodeValue()) {
+            } else if (keyCode == MouseKeyEvent.RELEASE.getKeyCode(inputDevice)) {
                 sendVirtualMouseButtonEvent(
                         VirtualMouseButtonEvent.BUTTON_PRIMARY,
                         VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE
                 );
-            } else if (isMouseButtonKey(keyCode)) {
+            } else if (isMouseButtonKey(keyCode, inputDevice)) {
                 performMouseButtonAction(keyCode);
-            } else if (mScrollToggleOn && isMouseScrollKey(keyCode)) {
+            } else if (mScrollToggleOn && isMouseScrollKey(keyCode, inputDevice)) {
                 // If the scroll key is pressed down and no other key is active,
                 // set it as the active key and send a message to scroll the pointer
                 if (mActiveScrollKey == KEY_NOT_SET) {
@@ -439,7 +523,8 @@
                 mHandler.removeMessages(MESSAGE_SCROLL_MOUSE_POINTER);
             } else {
                 Slog.i(LOG_TAG, "Dropping event with key code: '" + keyCode
-                        + "', with no matching down event from deviceId = " + event.getDeviceId());
+                        + "', with no matching down event from deviceId = "
+                        + event.getDeviceId());
             }
         }
     }
@@ -503,12 +588,40 @@
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Override
     public void onDestroy() {
-        // Clear mouse state
-        mActiveMoveKey = KEY_NOT_SET;
-        mActiveScrollKey = KEY_NOT_SET;
-        mLastTimeKeyActionPerformed = 0;
+        mHandler.post(() -> {
+            // Clear mouse state
+            mActiveMoveKey = KEY_NOT_SET;
+            mActiveScrollKey = KEY_NOT_SET;
+            mLastTimeKeyActionPerformed = 0;
+            mDeviceKeyCodeMap.clear();
+        });
 
         mHandler.removeCallbacksAndMessages(null);
         mVirtualDevice.close();
     }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        mDeviceKeyCodeMap.remove(deviceId);
+    }
+
+    /**
+     * The user can change the keyboard layout from settings at anytime, which would change
+     * key character map for that device. Hence, we should use this callback to
+     * update the key code to enum mapping if there is a change in the physical keyboard detected.
+     *
+     * @param deviceId The id of the input device that changed.
+     */
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+        // Update the enum mapping only if input device that changed is a keyboard
+        if (inputDevice.isFullKeyboard() && !mDeviceKeyCodeMap.contains(deviceId)) {
+            initializeDeviceToEnumMap(inputDevice);
+        }
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
index 83f57b2..950246f 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
@@ -87,7 +87,7 @@
     public Set<AndroidAccessibilityCheckerResult> maybeRunA11yChecker(
             List<AccessibilityNodeInfo> nodes, @Nullable String sourceEventClassName,
             ComponentName a11yServiceComponentName, @UserIdInt int userId) {
-        if (!shouldRunA11yChecker()) {
+        if (!shouldRunA11yChecker() || nodes.isEmpty()) {
             return Set.of();
         }
 
@@ -95,24 +95,33 @@
         String defaultBrowserName = mPackageManager.getDefaultBrowserPackageNameAsUser(userId);
 
         try {
+            AndroidAccessibilityCheckerResult.Builder commonResultBuilder =
+                    AccessibilityCheckerUtils.getCommonResultBuilder(nodes.getFirst(),
+                            sourceEventClassName, mPackageManager, a11yServiceComponentName);
+            if (commonResultBuilder == null) {
+                return Set.of();
+            }
             for (AccessibilityNodeInfo nodeInfo : nodes) {
-                // Skip browser results because they are mostly related to web content and not the
-                // browser app itself.
+                // Skip browser results because they are mostly related to web content and
+                // not the browser app itself.
                 if (nodeInfo.getPackageName() == null
                         || nodeInfo.getPackageName().toString().equals(defaultBrowserName)) {
                     continue;
                 }
-                List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(nodeInfo);
+                List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(
+                        nodeInfo);
                 Set<AndroidAccessibilityCheckerResult> filteredResults =
                         AccessibilityCheckerUtils.processResults(nodeInfo, checkResults,
-                                sourceEventClassName, mPackageManager, a11yServiceComponentName);
+                                commonResultBuilder);
                 allResults.addAll(filteredResults);
             }
             mCachedResults.addAll(allResults);
+            return allResults;
+
         } catch (RuntimeException e) {
             Slog.e(LOG_TAG, "An unknown error occurred while running a11y checker.", e);
+            return Set.of();
         }
-        return allResults;
     }
 
     private List<AccessibilityHierarchyCheckResult> runChecksOnNode(
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
index eb24b02..a739304 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -91,45 +91,55 @@
                             AccessibilityCheckClass.TRAVERSAL_ORDER_CHECK));
     // LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/a11ychecker/proto/a11ychecker.proto)
 
-    static Set<AndroidAccessibilityCheckerResult> processResults(
+
+    /**
+     * Returns AccessibilityCheckResultReported.Builder with the common fields for all nodes
+     * belonging in the same cache pre-filled.
+     */
+    static @Nullable AndroidAccessibilityCheckerResult.Builder getCommonResultBuilder(
             AccessibilityNodeInfo nodeInfo,
-            List<AccessibilityHierarchyCheckResult> checkResults,
             @Nullable String activityClassName,
             PackageManager packageManager,
             ComponentName a11yServiceComponentName) {
-        String appPackageName = nodeInfo.getPackageName().toString();
-        String nodePath = AccessibilityNodePathBuilder.createNodePath(nodeInfo);
-        if (nodePath == null) {
-            return Set.of();
+        if (nodeInfo.getPackageName() == null) {
+            return null;
         }
-        AndroidAccessibilityCheckerResult.Builder commonBuilder;
+        String appPackageName = nodeInfo.getPackageName().toString();
         try {
-            commonBuilder = AndroidAccessibilityCheckerResult.newBuilder()
+            return AndroidAccessibilityCheckerResult.newBuilder()
                     .setPackageName(appPackageName)
                     .setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
-                    .setUiElementPath(nodePath)
                     .setActivityName(
                             getActivityName(packageManager, appPackageName, activityClassName))
                     .setWindowTitle(getWindowTitle(nodeInfo))
                     .setSourceComponentName(a11yServiceComponentName)
-                    .setSourceVersionCode(
-                            getAppVersionCode(packageManager,
-                                    a11yServiceComponentName.getPackageName()));
+                    .setSourceVersionCode(getAppVersionCode(packageManager,
+                            a11yServiceComponentName.getPackageName()));
         } catch (PackageManager.NameNotFoundException e) {
             Slog.e(LOG_TAG, "Unknown package name", e);
+            return null;
+        }
+    }
+
+    static Set<AndroidAccessibilityCheckerResult> processResults(
+            AccessibilityNodeInfo nodeInfo,
+            List<AccessibilityHierarchyCheckResult> checkResults,
+            AndroidAccessibilityCheckerResult.Builder resultBuilder) {
+        String nodePath = AccessibilityNodePathBuilder.createNodePath(nodeInfo);
+        if (resultBuilder == null || nodePath == null) {
             return Set.of();
         }
-
         return checkResults.stream()
                 .filter(checkResult -> checkResult.getType()
                         == AccessibilityCheckResult.AccessibilityCheckResultType.ERROR
                         || checkResult.getType()
                         == AccessibilityCheckResult.AccessibilityCheckResultType.WARNING)
-                .map(checkResult -> new AndroidAccessibilityCheckerResult.Builder(
-                        commonBuilder).setResultCheckClass(
-                        getCheckClass(checkResult)).setResultType(
-                        getCheckResultType(checkResult)).setResultId(
-                        checkResult.getResultId()).build())
+                .map(checkResult -> new AndroidAccessibilityCheckerResult.Builder(resultBuilder)
+                        .setUiElementPath(nodePath)
+                        .setResultCheckClass(getCheckClass(checkResult))
+                        .setResultType(getCheckResultType(checkResult))
+                        .setResultId(checkResult.getResultId())
+                        .build())
                 .collect(Collectors.toUnmodifiableSet());
     }
 
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/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java
index 845249e..ab94e98 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MouseEventHandler.java
@@ -39,8 +39,14 @@
      * @param displayId The display that is being magnified
      */
     public void onEvent(MotionEvent event, int displayId) {
-        if (event.getAction() == ACTION_HOVER_MOVE
-                || (event.getAction() == ACTION_MOVE && event.getSource() == SOURCE_MOUSE)) {
+        // Ignore gesture events synthesized from the touchpad.
+        // TODO(b/354696546): Use synthesized pinch gestures to control scale.
+        boolean isSynthesizedFromTouchpad =
+                event.getClassification() != MotionEvent.CLASSIFICATION_NONE;
+
+        // Consume only move events from the mouse or hovers from any tool.
+        if (!isSynthesizedFromTouchpad && (event.getAction() == ACTION_HOVER_MOVE
+                || (event.getAction() == ACTION_MOVE && event.getSource() == SOURCE_MOUSE))) {
             final float eventX = event.getX();
             final float eventY = event.getY();
 
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
index 954651d..a2d467c 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java
@@ -16,8 +16,7 @@
 
 package com.android.server.appfunctions;
 
-import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
-
+import android.app.appfunctions.AppFunctionManagerConfiguration;
 import android.content.Context;
 
 import com.android.server.SystemService;
@@ -35,7 +34,7 @@
 
     @Override
     public void onStart() {
-        if (enableAppFunctionManager()) {
+        if (AppFunctionManagerConfiguration.isSupported(getContext())) {
             publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl);
         }
     }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 9c6bcdf..32b8d6b 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -25,6 +25,7 @@
 import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Binder;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -94,36 +95,39 @@
             targetUser = mCallerValidator.verifyTargetUserHandle(
                     requestInternal.getUserHandle(), validatedCallingPackage);
         } catch (SecurityException exception) {
-            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
-                    .Builder(ExecuteAppFunctionResponse.RESULT_DENIED,
-                    getExceptionMessage(exception)).build());
+            safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse
+                    .newFailure(ExecuteAppFunctionResponse.RESULT_DENIED,
+                            exception.getMessage(),
+                            /*extras=*/  null));
             return;
         }
 
         // TODO(b/354956319): Add and honor the new enterprise policies.
         if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
-            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+            safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse.newFailure(
                     ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
-                    "Cannot run on a device with a device owner or from the managed profile."
-            ).build());
+                    "Cannot run on a device with a device owner or from the managed profile.",
+                    /*extras=*/  null
+            ));
             return;
         }
 
         String targetPackageName = requestInternal.getClientRequest().getTargetPackageName();
         if (TextUtils.isEmpty(targetPackageName)) {
-            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+            safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse.newFailure(
                     ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT,
-                    "Target package name cannot be empty."
-            ).build());
+                    "Target package name cannot be empty.",
+                    /*extras=*/  null
+            ));
             return;
         }
 
         if (!mCallerValidator.verifyCallerCanExecuteAppFunction(
                 validatedCallingPackage, targetPackageName)) {
-            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
-                    .Builder(ExecuteAppFunctionResponse.RESULT_DENIED,
-                    "Caller does not have permission to execute the appfunction")
-                    .build());
+            safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse
+                    .newFailure(ExecuteAppFunctionResponse.RESULT_DENIED,
+                            "Caller does not have permission to execute the appfunction",
+                            /*extras=*/  null));
             return;
         }
 
@@ -131,16 +135,23 @@
                 targetPackageName,
                 targetUser);
         if (serviceIntent == null) {
-            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+            safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse.newFailure(
                     ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
-                    "Cannot find the target service."
-            ).build());
+                    "Cannot find the target service.",
+                    /*extras=*/  null
+            ));
             return;
         }
-        bindAppFunctionServiceUnchecked(requestInternal, serviceIntent, targetUser,
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            bindAppFunctionServiceUnchecked(requestInternal, serviceIntent, targetUser,
                 safeExecuteAppFunctionCallback,
                 /*bindFlags=*/ Context.BIND_AUTO_CREATE,
-                /*timeoutInMillis=*/ mServiceConfig.getExecutionTimeoutConfig());
+                /*timeoutInMillis=*/ mServiceConfig.getExecuteAppFunctionTimeoutMillis());
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     private void bindAppFunctionServiceUnchecked(
@@ -171,9 +182,10 @@
                                     }
                             );
                         } catch (Exception e) {
-                            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
-                                    .Builder(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
-                                    getExceptionMessage(e)).build());
+                            safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse
+                                    .newFailure(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+                                            e.getMessage(),
+                                            /*extras=*/  null));
                             serviceUsageCompleteListener.onCompleted();
                         }
                     }
@@ -181,33 +193,32 @@
                     @Override
                     public void onFailedToConnect() {
                         Slog.e(TAG, "Failed to connect to service");
-                        safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse
-                                .Builder(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
-                                "Failed to connect to AppFunctionService").build());
+                        safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse
+                                .newFailure(ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
+                                        "Failed to connect to AppFunctionService",
+                                        /*extras=*/  null));
                     }
 
                     @Override
                     public void onTimedOut() {
                         Slog.e(TAG, "Timed out");
                         safeExecuteAppFunctionCallback.onResult(
-                                new ExecuteAppFunctionResponse.Builder(
+                                ExecuteAppFunctionResponse.newFailure(
                                         ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
-                                        "Binding to AppFunctionService timed out."
-                                ).build());
+                                        "Binding to AppFunctionService timed out.",
+                                        /*extras=*/  null
+                                ));
                     }
                 }
         );
 
         if (!bindServiceResult) {
             Slog.e(TAG, "Failed to bind to the AppFunctionService");
-            safeExecuteAppFunctionCallback.onResult(new ExecuteAppFunctionResponse.Builder(
+            safeExecuteAppFunctionCallback.onResult(ExecuteAppFunctionResponse.newFailure(
                     ExecuteAppFunctionResponse.RESULT_TIMED_OUT,
-                    "Failed to bind the AppFunctionService."
-            ).build());
+                    "Failed to bind the AppFunctionService.",
+                    /*extras=*/  null
+            ));
         }
     }
-
-    private String getExceptionMessage(Exception exception) {
-        return exception.getMessage() == null ? "" : exception.getMessage();
-    }
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
index 35c6c78..4bc6e70 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfig.java
@@ -26,5 +26,5 @@
     /**
      * Returns the maximum time to wait for an app function execution to be complete.
      */
-    long getExecutionTimeoutConfig();
+    long getExecuteAppFunctionTimeoutMillis();
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
index b203ead..e090317 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/ServiceConfigImpl.java
@@ -22,16 +22,17 @@
  * Implementation of {@link ServiceConfig}
  */
 public class ServiceConfigImpl implements ServiceConfig {
-    static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT = "execution_timeout";
-    static final long DEFAULT_EXECUTION_TIMEOUT_MS = 5000L;
+    static final String DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT =
+            "execute_app_function_timeout_millis";
+    static final long DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS = 5000L;
 
 
     @Override
-    public long getExecutionTimeoutConfig() {
+    public long getExecuteAppFunctionTimeoutMillis() {
         return DeviceConfig.getLong(
                 NAMESPACE_APP_FUNCTIONS,
                 DEVICE_CONFIG_PROPERTY_EXECUTION_TIMEOUT,
-                DEFAULT_EXECUTION_TIMEOUT_MS
+                DEFAULT_EXECUTE_APP_FUNCTION_TIMEOUT_MS
         );
     }
 }
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..5dd4c25
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.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.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 asynchronously.
+ *
+ * <p>Converts the AppSearch Callback API to {@link AndroidFuture}.
+ */
+@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 AndroidFuture<AppSearchSession> getSessionAsync() {
+        return mSettableSessionFuture.thenApply(
+                result -> {
+                    if (result.isSuccess()) {
+                        return result.getResultValue();
+                    } else {
+                        throw new RuntimeException(failedResultToException(result));
+                    }
+                });
+    }
+
+    /** Gets the schema for a given app search session. */
+    public AndroidFuture<GetSchemaResponse> getSchema() {
+        return getSessionAsync()
+                .thenComposeAsync(
+                        session -> {
+                            AndroidFuture<AppSearchResult<GetSchemaResponse>>
+                                    settableSchemaResponse = new AndroidFuture<>();
+                            session.getSchema(mExecutor, settableSchemaResponse::complete);
+                            return settableSchemaResponse.thenApply(
+                                    result -> {
+                                        if (result.isSuccess()) {
+                                            return result.getResultValue();
+                                        } else {
+                                            throw new RuntimeException(
+                                                    failedResultToException(result));
+                                        }
+                                    });
+                        },
+                        mExecutor);
+    }
+
+    /** Sets the schema for a given app search session. */
+    public AndroidFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest setSchemaRequest) {
+        return getSessionAsync()
+                .thenComposeAsync(
+                        session -> {
+                            AndroidFuture<AppSearchResult<SetSchemaResponse>>
+                                    settableSchemaResponse = new AndroidFuture<>();
+                            session.setSchema(
+                                    setSchemaRequest,
+                                    mExecutor,
+                                    mExecutor,
+                                    settableSchemaResponse::complete);
+                            return settableSchemaResponse.thenApply(
+                                    result -> {
+                                        if (result.isSuccess()) {
+                                            return result.getResultValue();
+                                        } else {
+                                            throw new RuntimeException(
+                                                    failedResultToException(result));
+                                        }
+                                    });
+                        },
+                        mExecutor);
+    }
+
+    @Override
+    public void close() throws IOException {
+        try {
+            getSessionAsync().get().close();
+        } catch (Exception ex) {
+            Slog.e(TAG, "Failed to close app search session", ex);
+        }
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 14a3211..dc0b4b8 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -48,6 +48,7 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutofillWindowPresenter;
 import android.widget.BaseAdapter;
@@ -82,7 +83,6 @@
             com.android.internal.R.style.Theme_DeviceDefault_Light_Autofill;
     private static final int THEME_ID_DARK =
             com.android.internal.R.style.Theme_DeviceDefault_Autofill;
-    private static final int AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS = 5;
 
     private static final TypedValue sTempTypedValue = new TypedValue();
 
@@ -113,9 +113,11 @@
 
     private final @NonNull Callback mCallback;
 
+    private final @NonNull WindowManager mWindowManager;
+
     private final @Nullable View mHeader;
     private final @NonNull ListView mListView;
-    private final @Nullable View mFooter;
+    private @Nullable View mFooter;
 
     private final @Nullable ItemsAdapter mAdapter;
 
@@ -134,6 +136,8 @@
 
     private int mMaxInputLengthForAutofill;
 
+    private final boolean mIsCredmanAutofillSession;
+
     public static boolean isFullScreen(Context context) {
         if (sFullScreenMode != null) {
             if (sVerbose) Slog.v(TAG, "forcing full-screen mode to " + sFullScreenMode);
@@ -158,6 +162,9 @@
         mContext = new ContextThemeWrapper(context, mThemeId);
         mUserContext = Helper.getUserContext(mContext);
         mMaxInputLengthForAutofill = maxInputLengthForAutofill;
+        mIsCredmanAutofillSession = (Flags.autofillCredmanIntegration()
+            && ((response.getFlags() & FLAG_CREDENTIAL_MANAGER_RESPONSE) != 0));
+        mWindowManager = mContext.getSystemService(WindowManager.class);
 
         final LayoutInflater inflater = LayoutInflater.from(mContext);
 
@@ -167,7 +174,8 @@
         final ViewGroup decor;
         if (mFullScreen) {
             decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_fullscreen, null);
-        } else if (headerPresentation != null || footerPresentation != null) {
+        } else if (headerPresentation != null
+                || footerPresentation != null || mIsCredmanAutofillSession) {
             decor = (ViewGroup) inflater.inflate(R.layout.autofill_dataset_picker_header_footer,
                     null);
         } else {
@@ -219,11 +227,7 @@
             if (sVerbose) {
                 Slog.v(TAG, "overriding maximum visible datasets to " + mVisibleDatasetsMaxCount);
             }
-        } else if (Flags.autofillCredmanIntegration() && (
-                (response.getFlags() & FLAG_CREDENTIAL_MANAGER_RESPONSE) != 0)) {
-            mVisibleDatasetsMaxCount = AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS;
-        }
-        else {
+        } else {
             mVisibleDatasetsMaxCount = mContext.getResources()
                     .getInteger(com.android.internal.R.integer.autofill_max_visible_datasets);
         }
@@ -301,7 +305,7 @@
                 mHeader = null;
             }
 
-            if (footerPresentation != null) {
+            if (footerPresentation != null && !mIsCredmanAutofillSession) {
                 final LinearLayout footerContainer =
                         decor.findViewById(R.id.autofill_dataset_footer);
                 if (footerContainer != null) {
@@ -366,7 +370,22 @@
                     }
 
                     applyCancelAction(view, response.getCancelIds());
-                    items.add(new ViewItem(dataset, filterPattern, filterable, valueText, view));
+                    if (AutofillManager.PINNED_DATASET_ID.equals(dataset.getId())
+                            && mIsCredmanAutofillSession && !items.isEmpty()) {
+                        final LinearLayout footerContainer =
+                                decor.findViewById(R.id.autofill_dataset_footer);
+                        if (sVerbose) {
+                          Slog.v(TAG, "adding footer");
+                        }
+                        mFooter = view;
+                        footerContainer.addView(mFooter);
+                        footerContainer.setVisibility(View.VISIBLE);
+                        footerContainer.setClickable(true);
+                        footerContainer.setOnClickListener(v -> mCallback.onDatasetPicked(dataset));
+                    } else {
+                        items.add(
+                            new ViewItem(dataset, filterPattern, filterable, valueText, view));
+                    }
                 }
             }
 
@@ -459,12 +478,9 @@
                 if (updateContentSize()) {
                     requestShowFillUi();
                 }
-                if (mAdapter.getCount() > mVisibleDatasetsMaxCount) {
-                    mListView.setVerticalScrollBarEnabled(true);
-                    mListView.onVisibilityAggregated(true);
-                } else {
-                    mListView.setVerticalScrollBarEnabled(false);
-                }
+                mListView.setVerticalScrollBarEnabled(true);
+                mListView.onVisibilityAggregated(true);
+
                 if (mAdapter.getCount() != oldCount) {
                     mListView.requestLayout();
                 }
@@ -578,11 +594,18 @@
         return changed;
     }
 
+    private boolean heightLesserThanDisplayScreen(int height) {
+        // Don't update list height for credential options beyond 80% of display window even if we
+        // are still under the max visible number of datasets. This could happen when font or
+        // display size is set to large.
+        return height < (0.8 * mWindowManager.getCurrentWindowMetrics().getBounds().height());
+    }
+
     private boolean updateHeight(View view, Point maxSize) {
         boolean changed = false;
         final int clampedMeasuredHeight = Math.min(view.getMeasuredHeight(), maxSize.y);
         final int newContentHeight = mContentHeight + clampedMeasuredHeight;
-        if (newContentHeight != mContentHeight) {
+        if (newContentHeight != mContentHeight && heightLesserThanDisplayScreen(newContentHeight)) {
             mContentHeight = newContentHeight;
             changed = true;
         }
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 8abbe56..22eefb3 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -73,6 +73,8 @@
 
 /**
  * Utility methods to read backup tar file.
+ * Exteranl depenency:
+ *  <li> @android.provider.Settings.Secure.V_TO_U_RESTORE_ALLOWLIST
  */
 public class TarBackupReader {
     private static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
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/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index b3a2da4..d56f17b 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -20,6 +20,7 @@
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 import static android.companion.CompanionDeviceManager.RESULT_INTERNAL_ERROR;
+import static android.companion.CompanionDeviceManager.RESULT_SECURITY_ERROR;
 import static android.content.ComponentName.createRelative;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 
@@ -40,6 +41,7 @@
 import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
+import android.companion.Flags;
 import android.companion.IAssociationRequestCallback;
 import android.content.ComponentName;
 import android.content.Context;
@@ -182,7 +184,11 @@
             String errorMessage = "3p apps are not allowed to create associations on watch.";
             Slog.e(TAG, errorMessage);
             try {
-                callback.onFailure(RESULT_INTERNAL_ERROR);
+                if (Flags.associationFailureCode()) {
+                    callback.onFailure(RESULT_SECURITY_ERROR, errorMessage);
+                } else {
+                    callback.onFailure(RESULT_INTERNAL_ERROR, errorMessage);
+                }
             } catch (RemoteException e) {
                 // ignored
             }
@@ -251,9 +257,12 @@
         } catch (SecurityException e) {
             // Since, at this point the caller is our own UI, we need to catch the exception on
             // forward it back to the application via the callback.
-            Slog.e(TAG, e.getMessage());
             try {
-                callback.onFailure(RESULT_INTERNAL_ERROR);
+                if (Flags.associationFailureCode()) {
+                    callback.onFailure(RESULT_SECURITY_ERROR, e.getMessage());
+                } else {
+                    callback.onFailure(RESULT_INTERNAL_ERROR, e.getMessage());
+                }
             } catch (RemoteException ignore) {
             }
             return;
@@ -378,7 +387,7 @@
             // Send the association back via the app's callback
             if (callback != null) {
                 try {
-                    callback.onFailure(RESULT_INTERNAL_ERROR);
+                    callback.onFailure(RESULT_INTERNAL_ERROR, "Association doesn't exist.");
                 } catch (RemoteException ignore) {
                 }
             }
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/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 2119622..4b9065b 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -27,7 +27,6 @@
 import android.annotation.UserIdInt;
 import android.app.WindowConfiguration;
 import android.app.compat.CompatChanges;
-import android.companion.virtual.VirtualDeviceManager.ActivityListener;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.AttributionSource;
@@ -61,6 +60,9 @@
 
     private static final String TAG = "GenericWindowPolicyController";
 
+    private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT =
+            new ComponentName("android", BlockedAppStreamingActivity.class.getName());
+
     /** Interface to listen running applications change on virtual display. */
     public interface RunningAppsChangedListener {
         /**
@@ -69,29 +71,25 @@
         void onRunningAppsChanged(ArraySet<Integer> runningUids);
     }
 
-    /**
-     * For communicating when activities are blocked from running on the display by this policy
-     * controller.
-     */
-    public interface ActivityBlockedCallback {
+    /** Interface to react to activity changes on the virtual display. */
+    public interface ActivityListener {
+
+        /** Called when the top activity changes. */
+        void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
+                @UserIdInt int userId);
+
+        /** Called when the display becomes empty. */
+        void onDisplayEmpty(int displayId);
+
         /** Called when an activity is blocked.*/
-        void onActivityBlocked(int displayId, ActivityInfo activityInfo, IntentSender intentSender);
-    }
-    private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT =
-            new ComponentName("android", BlockedAppStreamingActivity.class.getName());
+        void onActivityLaunchBlocked(int displayId, @NonNull ActivityInfo activityInfo,
+                @Nullable IntentSender intentSender);
 
-    /**
-     * For communicating when a secure window shows on the virtual display.
-     */
-    public interface SecureWindowCallback {
         /** Called when a secure window shows on the virtual display. */
-        void onSecureWindowShown(int displayId, int uid);
-    }
+        void onSecureWindowShown(int displayId, @NonNull ActivityInfo activityInfo);
 
-    /** Interface to listen for interception of intents. */
-    public interface IntentListenerCallback {
         /** Returns true when an intent should be intercepted */
-        boolean shouldInterceptIntent(Intent intent);
+        boolean shouldInterceptIntent(@NonNull Intent intent);
     }
 
     /**
@@ -118,7 +116,6 @@
     private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
     @NonNull
     private final Object mGenericWindowPolicyControllerLock = new Object();
-    @Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
 
     // Do not access mDisplayId and mIsMirrorDisplay directly, instead use waitAndGetDisplayId()
     // and waitAndGetIsMirrorDisplay()
@@ -129,14 +126,12 @@
     @NonNull
     @GuardedBy("mGenericWindowPolicyControllerLock")
     private final ArraySet<Integer> mRunningUids = new ArraySet<>();
-    @Nullable private final ActivityListener mActivityListener;
-    @Nullable private final IntentListenerCallback mIntentListenerCallback;
+    @NonNull private final ActivityListener mActivityListener;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     @NonNull
     @GuardedBy("mGenericWindowPolicyControllerLock")
     private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
             new ArraySet<>();
-    @Nullable private final SecureWindowCallback mSecureWindowCallback;
     @NonNull private final Set<String> mDisplayCategories;
 
     @GuardedBy("mGenericWindowPolicyControllerLock")
@@ -162,12 +157,6 @@
      * @param crossTaskNavigationExemptions The set of components explicitly exempt from the default
      *   navigation policy.
      * @param activityListener Activity listener to listen for activity changes.
-     * @param activityBlockedCallback Callback that is called when an activity is blocked from
-     *   launching.
-     * @param secureWindowCallback Callback that is called when a secure window shows on the
-     *   virtual display.
-     * @param intentListenerCallback Callback that is called to intercept intents when matching
-     *   passed in filters.
      * @param showTasksInHostDeviceRecents whether to show activities in recents on the host device.
      * @param customHomeComponent The component acting as a home activity on the virtual display. If
      *   {@code null}, then the system-default secondary home activity will be used. This is only
@@ -184,10 +173,7 @@
             @NonNull Set<String> activityPolicyPackageExemptions,
             boolean crossTaskNavigationAllowedByDefault,
             @NonNull Set<ComponentName> crossTaskNavigationExemptions,
-            @Nullable ActivityListener activityListener,
-            @Nullable ActivityBlockedCallback activityBlockedCallback,
-            @Nullable SecureWindowCallback secureWindowCallback,
-            @Nullable IntentListenerCallback intentListenerCallback,
+            @NonNull ActivityListener activityListener,
             @NonNull Set<String> displayCategories,
             boolean showTasksInHostDeviceRecents,
             @Nullable ComponentName customHomeComponent) {
@@ -199,11 +185,8 @@
         mActivityPolicyPackageExemptions = new ArraySet<>(activityPolicyPackageExemptions);
         mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
         mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
-        mActivityBlockedCallback = activityBlockedCallback;
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
         mActivityListener = activityListener;
-        mSecureWindowCallback = secureWindowCallback;
-        mIntentListenerCallback = intentListenerCallback;
         mDisplayCategories = displayCategories;
         mShowTasksInHostDeviceRecents = showTasksInHostDeviceRecents;
         mCustomHomeComponent = customHomeComponent;
@@ -306,8 +289,7 @@
             @Nullable Intent intent, @WindowConfiguration.WindowingMode int windowingMode,
             int launchingFromDisplayId, boolean isNewTask, boolean isResultExpected,
             @Nullable Supplier<IntentSender> intentSender) {
-        if (mIntentListenerCallback != null && intent != null
-                && mIntentListenerCallback.shouldInterceptIntent(intent)) {
+        if (intent != null && mActivityListener.shouldInterceptIntent(intent)) {
             logActivityLaunchBlocked("Virtual device intercepting intent");
             return false;
         }
@@ -391,11 +373,9 @@
         int displayId = waitAndGetDisplayId();
         // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
         // aware that the virtual display has a secure window on top.
-        if ((windowFlags & FLAG_SECURE) != 0 && mSecureWindowCallback != null
-                && displayId != INVALID_DISPLAY) {
+        if ((windowFlags & FLAG_SECURE) != 0 && displayId != INVALID_DISPLAY) {
             // Post callback on the main thread, so it doesn't block activity launching.
-            mHandler.post(() -> mSecureWindowCallback.onSecureWindowShown(displayId,
-                    activityInfo.applicationInfo.uid));
+            mHandler.post(() -> mActivityListener.onSecureWindowShown(displayId, activityInfo));
         }
 
         if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
@@ -418,7 +398,7 @@
         // Don't send onTopActivityChanged() callback when topActivity is null because it's defined
         // as @NonNull in ActivityListener interface. Sends onDisplayEmpty() callback instead when
         // there is no activity running on virtual display.
-        if (mActivityListener != null && topActivity != null && displayId != INVALID_DISPLAY) {
+        if (topActivity != null && displayId != INVALID_DISPLAY) {
             // Post callback on the main thread so it doesn't block activity launching
             mHandler.post(() ->
                     mActivityListener.onTopActivityChanged(displayId, topActivity, userId));
@@ -431,8 +411,7 @@
             mRunningUids.clear();
             mRunningUids.addAll(runningUids);
             int displayId = waitAndGetDisplayId();
-            if (mActivityListener != null && mRunningUids.isEmpty()
-                    && displayId != INVALID_DISPLAY) {
+            if (mRunningUids.isEmpty() && displayId != INVALID_DISPLAY) {
                 // Post callback on the main thread so it doesn't block activity launching
                 mHandler.post(() -> mActivityListener.onDisplayEmpty(displayId));
             }
@@ -482,9 +461,8 @@
         int displayId = waitAndGetDisplayId();
         // Don't trigger activity blocked callback for mirror displays, because we can't show
         // any activity or presentation on it anyway.
-        if (!waitAndGetIsMirrorDisplay() && mActivityBlockedCallback != null
-                && displayId != INVALID_DISPLAY) {
-            mActivityBlockedCallback.onActivityBlocked(displayId, activityInfo,
+        if (!waitAndGetIsMirrorDisplay() && displayId != INVALID_DISPLAY) {
+            mActivityListener.onActivityLaunchBlocked(displayId, activityInfo,
                     intentSender == null ? null : intentSender.get());
         }
         Counter.logIncrementWithUid(
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 8da58cf..d3e808f 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -689,13 +689,15 @@
     }
 
     /** A helper class used to wait for an input device to be registered. */
-    private class WaitForDevice implements  AutoCloseable {
+    private class WaitForDevice implements AutoCloseable {
         private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
+        private final String mDeviceName;
         private final InputManager.InputDeviceListener mListener;
 
         private int mInputDeviceId = IInputConstants.INVALID_INPUT_DEVICE_ID;
 
         WaitForDevice(String deviceName, int vendorId, int productId, int associatedDisplayId) {
+            mDeviceName = deviceName;
             mListener = new InputManager.InputDeviceListener() {
                 @Override
                 public void onInputDeviceAdded(int deviceId) {
@@ -741,15 +743,17 @@
             try {
                 if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
                     throw new DeviceCreationException(
-                            "Timed out waiting for virtual device to be created.");
+                            "Timed out waiting for virtual input device " + mDeviceName
+                                    + " to be created.");
                 }
             } catch (InterruptedException e) {
                 throw new DeviceCreationException(
-                        "Interrupted while waiting for virtual device to be created.", e);
+                        "Interrupted while waiting for virtual input device " + mDeviceName
+                                + " to be created.", e);
             }
             if (mInputDeviceId == IInputConstants.INVALID_INPUT_DEVICE_ID) {
                 throw new IllegalStateException(
-                        "Virtual input device was created with an invalid "
+                        "Virtual input device " + mDeviceName + " was created with an invalid "
                                 + "id=" + mInputDeviceId);
             }
             return mInputDeviceId;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 4eb50a9..cd2dd3a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -40,6 +40,7 @@
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
+import android.app.compat.CompatChanges;
 import android.companion.AssociationInfo;
 import android.companion.virtual.ActivityPolicyExemption;
 import android.companion.virtual.IVirtualDevice;
@@ -48,7 +49,6 @@
 import android.companion.virtual.IVirtualDeviceSoundEffectListener;
 import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.VirtualDeviceManager.ActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -56,6 +56,8 @@
 import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Context;
@@ -88,6 +90,7 @@
 import android.media.AudioManager;
 import android.media.audiopolicy.AudioMix;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.LocaleList;
 import android.os.Looper;
@@ -132,6 +135,16 @@
 
     private static final String TAG = "VirtualDeviceImpl";
 
+    /**
+     * Do not show a toast on the virtual display when a secure surface is shown after
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. VDM clients should use
+     * {@link VirtualDeviceManager.ActivityListener#onSecureWindowShown} instead to provide
+     * a custom notification if desired.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long DO_NOT_SHOW_TOAST_WHEN_SECURE_SURFACE_SHOWN = 311101667L;
+
     private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
             DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
@@ -182,7 +195,7 @@
     @GuardedBy("mVirtualDeviceLock")
     private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
     private IVirtualDeviceActivityListener mActivityListener;
-    private ActivityListener mActivityListenerAdapter = null;
+    private GenericWindowPolicyController.ActivityListener mActivityListenerAdapter = null;
     private IVirtualDeviceSoundEffectListener mSoundEffectListener;
     private final DisplayManagerGlobal mDisplayManager;
     private final DisplayManagerInternal mDisplayManagerInternal;
@@ -207,50 +220,122 @@
     @NonNull
     private final Set<String> mActivityPolicyPackageExemptions = new ArraySet<>();
 
-    private ActivityListener createListenerAdapter() {
-        return new ActivityListener() {
+    private class GwpcActivityListener implements GenericWindowPolicyController.ActivityListener {
 
-            @Override
-            public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity) {
-                try {
-                    mActivityListener.onTopActivityChanged(displayId, topActivity,
-                            UserHandle.USER_NULL);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
-                }
+        @Override
+        public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
+                @UserIdInt int userId) {
+            try {
+                mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
+            }
+        }
+
+        @Override
+        public void onDisplayEmpty(int displayId) {
+            try {
+                mActivityListener.onDisplayEmpty(displayId);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
+            }
+        }
+
+        @Override
+        public void onActivityLaunchBlocked(int displayId, @NonNull ActivityInfo activityInfo,
+                @Nullable IntentSender intentSender) {
+            Intent intent =
+                    BlockedAppStreamingActivity.createIntent(activityInfo, getDisplayName());
+            if (shouldShowBlockedActivityDialog(
+                    activityInfo.getComponentName(), intent.getComponent())) {
+                mContext.startActivityAsUser(
+                        intent.addFlags(
+                                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
+                        ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
+                        UserHandle.SYSTEM);
             }
 
-            @Override
-            public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
-                    @UserIdInt int userId) {
-                try {
-                    mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
-                }
-            }
-
-            @Override
-            public void onDisplayEmpty(int displayId) {
-                try {
-                    mActivityListener.onDisplayEmpty(displayId);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
-                }
-            }
-
-            @Override
-            public void onActivityLaunchBlocked(int displayId,
-                    @NonNull ComponentName componentName, @NonNull UserHandle user,
-                    @Nullable IntentSender intentSender) {
+            if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
                 try {
                     mActivityListener.onActivityLaunchBlocked(
-                            displayId, componentName, user, intentSender);
+                            displayId,
+                            activityInfo.getComponentName(),
+                            UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid),
+                            intentSender);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
                 }
             }
-        };
+        }
+
+        @Override
+        public void onSecureWindowShown(int displayId, @NonNull ActivityInfo activityInfo) {
+            if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+                try {
+                    mActivityListener.onSecureWindowShown(
+                            displayId,
+                            activityInfo.getComponentName(),
+                            UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid));
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
+                }
+
+                if (CompatChanges.isChangeEnabled(DO_NOT_SHOW_TOAST_WHEN_SECURE_SURFACE_SHOWN,
+                        mOwnerPackageName,  UserHandle.getUserHandleForUid(mOwnerUid))) {
+                    return;
+                }
+            }
+
+            // If a virtual display isn't secure, the screen can't be captured. Show a warning toast
+            // if the secure window is shown on a non-secure virtual display.
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(displayId);
+            if ((display.getFlags() & Display.FLAG_SECURE) == 0) {
+                showToastWhereUidIsRunning(activityInfo.applicationInfo.uid,
+                        com.android.internal.R.string.vdm_secure_window,
+                        Toast.LENGTH_LONG, mContext.getMainLooper());
+
+                Counter.logIncrementWithUid(
+                        "virtual_devices.value_secure_window_blocked_count",
+                        mAttributionSource.getUid());
+            }
+        }
+
+        /**
+         * Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true
+         * if the intent matches any filter notifying the DisplayPolicyController to abort the
+         * activity launch to be replaced by the interception.
+         */
+        @Override
+        public boolean shouldInterceptIntent(@NonNull Intent intent) {
+            synchronized (mVirtualDeviceLock) {
+                boolean hasInterceptedIntent = false;
+                for (Map.Entry<IBinder, IntentFilter> interceptor
+                        : mIntentInterceptors.entrySet()) {
+                    IntentFilter intentFilter = interceptor.getValue();
+                    // Explicitly match the actions because the intent filter will match any intent
+                    // without an explicit action. If the intent has no action, then require that
+                    // there are no actions specified in the filter either.
+                    boolean explicitActionMatch =
+                            intent.getAction() != null || intentFilter.countActions() == 0;
+                    if (explicitActionMatch && intentFilter.match(
+                            intent.getAction(), intent.getType(), intent.getScheme(),
+                            intent.getData(), intent.getCategories(), TAG) >= 0) {
+                        try {
+                            // For privacy reasons, only returning the intents action and data.
+                            // Any other required field will require a review.
+                            IVirtualDeviceIntentInterceptor.Stub.asInterface(interceptor.getKey())
+                                    .onIntentIntercepted(
+                                            new Intent(intent.getAction(), intent.getData()));
+                            hasInterceptedIntent = true;
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Unable to call mActivityListener", e);
+                        }
+                    }
+                }
+                return hasInterceptedIntent;
+            }
+        }
     }
 
     VirtualDeviceImpl(
@@ -1290,7 +1375,7 @@
                 Flags.vdmCustomHome() ? mParams.getHomeComponent() : null;
 
         if (mActivityListenerAdapter == null) {
-            mActivityListenerAdapter = createListenerAdapter();
+            mActivityListenerAdapter = new GwpcActivityListener();
         }
 
         final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
@@ -1306,9 +1391,6 @@
                         ? mParams.getBlockedCrossTaskNavigations()
                         : mParams.getAllowedCrossTaskNavigations(),
                 mActivityListenerAdapter,
-                this::onActivityBlocked,
-                this::onSecureWindowShown,
-                this::shouldInterceptIntent,
                 displayCategories,
                 showTasksInHostDeviceRecents,
                 homeComponent);
@@ -1378,28 +1460,6 @@
         }
     }
 
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
-    private void onActivityBlocked(int displayId, ActivityInfo activityInfo,
-            IntentSender intentSender) {
-        Intent intent = BlockedAppStreamingActivity.createIntent(activityInfo, getDisplayName());
-        if (shouldShowBlockedActivityDialog(
-                activityInfo.getComponentName(), intent.getComponent())) {
-            mContext.startActivityAsUser(
-                    intent.addFlags(
-                            Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
-                    ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
-                    UserHandle.SYSTEM);
-        }
-
-        if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
-            mActivityListenerAdapter.onActivityLaunchBlocked(
-                    displayId,
-                    activityInfo.getComponentName(),
-                    UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid),
-                    intentSender);
-        }
-    }
-
     private boolean shouldShowBlockedActivityDialog(ComponentName blockedComponent,
             ComponentName blockedAppStreamingActivityComponent) {
         if (Objects.equals(blockedComponent, blockedAppStreamingActivityComponent)) {
@@ -1414,27 +1474,6 @@
         return getDevicePolicy(POLICY_TYPE_BLOCKED_ACTIVITY) == DEVICE_POLICY_DEFAULT;
     }
 
-    private void onSecureWindowShown(int displayId, int uid) {
-        synchronized (mVirtualDeviceLock) {
-            if (!mVirtualDisplays.contains(displayId)) {
-                return;
-            }
-        }
-
-        // If a virtual display isn't secure, the screen can't be captured. Show a warning toast
-        // if the secure window is shown on a non-secure virtual display.
-        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-        Display display = displayManager.getDisplay(displayId);
-        if ((display.getFlags() & Display.FLAG_SECURE) == 0) {
-            showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
-                    Toast.LENGTH_LONG, mContext.getMainLooper());
-
-            Counter.logIncrementWithUid(
-                    "virtual_devices.value_secure_window_blocked_count",
-                    mAttributionSource.getUid());
-        }
-    }
-
     private ArraySet<UserHandle> getAllowedUserHandles() {
         ArraySet<UserHandle> result = new ArraySet<>();
         final long token = Binder.clearCallingIdentity();
@@ -1621,40 +1660,6 @@
         }
     }
 
-    /**
-     * Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true if
-     * the intent matches any filter notifying the DisplayPolicyController to abort the
-     * activity launch to be replaced by the interception.
-     */
-    private boolean shouldInterceptIntent(Intent intent) {
-        synchronized (mVirtualDeviceLock) {
-            boolean hasInterceptedIntent = false;
-            for (Map.Entry<IBinder, IntentFilter> interceptor : mIntentInterceptors.entrySet()) {
-                IntentFilter intentFilter = interceptor.getValue();
-                // Explicitly match the actions because the intent filter will match any intent
-                // without an explicit action. If the intent has no action, then require that there
-                // are no actions specified in the filter either.
-                boolean explicitActionMatch =
-                        intent.getAction() != null || intentFilter.countActions() == 0;
-                if (explicitActionMatch && intentFilter.match(
-                        intent.getAction(), intent.getType(), intent.getScheme(), intent.getData(),
-                        intent.getCategories(), TAG) >= 0) {
-                    try {
-                        // For privacy reasons, only returning the intents action and data. Any
-                        // other required field will require a review.
-                        IVirtualDeviceIntentInterceptor.Stub.asInterface(interceptor.getKey())
-                            .onIntentIntercepted(new Intent(intent.getAction(), intent.getData()));
-                        hasInterceptedIntent = true;
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Unable to call mVirtualDeviceIntentInterceptor", e);
-                    }
-                }
-            }
-
-            return hasInterceptedIntent;
-        }
-    }
-
     interface PendingTrampolineCallback {
         /**
          * Called when the callback should start waiting for the given pending trampoline.
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/Android.bp b/services/core/Android.bp
index 89d7961..1b5b7e8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -233,6 +233,7 @@
         "stats_flags_lib",
         "core_os_flags_lib",
         "connectivity_flags_lib",
+        "device_config_service_flags_java",
         "dreams_flags_lib",
         "aconfig_new_storage_flags_lib",
         "powerstats_flags_lib",
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/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index e84250d..fbe593f 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -20,6 +20,8 @@
 import static android.content.Intent.ACTION_SHUTDOWN;
 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
 
+import static com.android.server.crashrecovery.CrashRecoveryUtils.dumpCrashRecoveryEvents;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
@@ -44,6 +46,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.IndentingPrintWriter;
 import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.Xml;
@@ -51,7 +54,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -72,6 +74,7 @@
 import java.io.InputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -296,7 +299,9 @@
                     this::onSyncRequestNotified);
             setPropertyChangedListenerLocked();
             updateConfigs();
-            registerConnectivityModuleHealthListener();
+            if (!Flags.refactorCrashrecovery()) {
+                registerConnectivityModuleHealthListener();
+            }
         }
     }
 
@@ -1263,18 +1268,21 @@
 
 
     /** Dump status of every observer in mAllObservers. */
-    public void dump(IndentingPrintWriter pw) {
-        pw.println("Package Watchdog status");
-        pw.increaseIndent();
+    public void dump(PrintWriter pw) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        ipw.println("Package Watchdog status");
+        ipw.increaseIndent();
         synchronized (mLock) {
             for (String observerName : mAllObservers.keySet()) {
-                pw.println("Observer name: " + observerName);
-                pw.increaseIndent();
+                ipw.println("Observer name: " + observerName);
+                ipw.increaseIndent();
                 ObserverInternal observerInternal = mAllObservers.get(observerName);
-                observerInternal.dump(pw);
-                pw.decreaseIndent();
+                observerInternal.dump(ipw);
+                ipw.decreaseIndent();
             }
         }
+        ipw.decreaseIndent();
+        dumpCrashRecoveryEvents(ipw);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index bba97fa..cadceb5 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -18,7 +18,7 @@
 
 import static android.provider.DeviceConfig.Properties;
 
-import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -291,13 +291,13 @@
                 Properties properties = new Properties.Builder(namespaceToReset).build();
                 try {
                     if (!DeviceConfig.setProperties(properties)) {
-                        logCriticalInfo(Log.ERROR, "Failed to clear properties under "
+                        logCrashRecoveryEvent(Log.ERROR, "Failed to clear properties under "
                             + namespaceToReset
                             + ". Running `device_config get_sync_disabled_for_tests` will confirm"
                             + " if config-bulk-update is enabled.");
                     }
                 } catch (DeviceConfig.BadConfigException exception) {
-                    logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
+                    logCrashRecoveryEvent(Log.WARN, "namespace " + namespaceToReset
                             + " is already banned, skip reset.");
                 }
             }
@@ -528,7 +528,7 @@
             if (!TextUtils.isEmpty(failedPackage)) {
                 successMsg += " for package " + failedPackage;
             }
-            logCriticalInfo(Log.DEBUG, successMsg);
+            logCrashRecoveryEvent(Log.DEBUG, successMsg);
         } catch (Throwable t) {
             logRescueException(level, failedPackage, t);
         }
@@ -687,7 +687,7 @@
         if (!TextUtils.isEmpty(failedPackageName)) {
             failureMsg += " for package " + failedPackageName;
         }
-        logCriticalInfo(Log.ERROR, failureMsg + ": " + msg);
+        logCrashRecoveryEvent(Log.ERROR, failureMsg + ": " + msg);
     }
 
     private static int mapRescueLevelToUserImpact(int rescueLevel) {
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index 82c2038..dbf144f 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -56,16 +56,11 @@
         }
     }
 
-    @android.ravenwood.annotation.RavenwoodReplace
     private static String[] getSerialPorts(Context context) {
         return context.getResources().getStringArray(
                 com.android.internal.R.array.config_serialPorts);
     }
 
-    private static String[] getSerialPorts$ravenwood(Context context) {
-        return new String[0];
-    }
-
     public static class Lifecycle extends SystemService {
         private SerialService mService;
 
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 19279a8..d86bae1 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -224,6 +224,9 @@
     /** Extended timeout for the system server watchdog for vold#partition operation. */
     private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
 
+    private static final Pattern OBB_FILE_PATH = Pattern.compile(
+            "(?i)(^/storage/[^/]+/(?:([0-9]+)/)?Android/obb/)([^/]+)/([^/]+\\.obb)");
+
     @GuardedBy("mLock")
     private final Set<Integer> mFuseMountedUser = new ArraySet<>();
 
@@ -3144,7 +3147,9 @@
         Objects.requireNonNull(rawPath, "rawPath cannot be null");
         Objects.requireNonNull(canonicalPath, "canonicalPath cannot be null");
         Objects.requireNonNull(token, "token cannot be null");
-        Objects.requireNonNull(obbInfo, "obbIfno cannot be null");
+        Objects.requireNonNull(obbInfo, "obbInfo cannot be null");
+
+        validateObbInfo(obbInfo, rawPath);
 
         final int callingUid = Binder.getCallingUid();
         final ObbState obbState = new ObbState(rawPath, canonicalPath,
@@ -3156,6 +3161,34 @@
             Slog.i(TAG, "Send to OBB handler: " + action.toString());
     }
 
+    private void validateObbInfo(ObbInfo obbInfo, String rawPath) {
+        String obbFilePath;
+        try {
+            obbFilePath = new File(rawPath).getCanonicalPath();
+        } catch (IOException ex) {
+            throw new RuntimeException("Failed to resolve path" + rawPath + " : " + ex);
+        }
+
+        Matcher matcher = OBB_FILE_PATH.matcher(obbFilePath);
+
+        if (matcher.matches()) {
+            int userId = UserHandle.getUserId(Binder.getCallingUid());
+            String pathUserId = matcher.group(2);
+            String pathPackageName = matcher.group(3);
+            if ((pathUserId != null && Integer.parseInt(pathUserId) != userId)
+                    || (pathUserId == null && userId != mCurrentUserId)) {
+                throw new SecurityException(
+                        "Path " + obbFilePath + "does not correspond to calling userId " + userId);
+            }
+            if (obbInfo != null && !obbInfo.packageName.equals(pathPackageName)) {
+                throw new SecurityException("Path " + obbFilePath
+                        + " does not contain package name " + pathPackageName);
+            }
+        } else {
+            throw new SecurityException("Invalid path to Obb file : " + obbFilePath);
+        }
+    }
+
     @Override
     public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) {
         Objects.requireNonNull(rawPath, "rawPath cannot be null");
@@ -3527,7 +3560,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 68d9221..e13b0a4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -50,7 +50,6 @@
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
 import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
@@ -101,15 +100,12 @@
 import static android.os.Process.BLUETOOTH_UID;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.INVALID_UID;
-import static android.os.Process.NETWORK_STACK_UID;
-import static android.os.Process.NFC_UID;
 import static android.os.Process.PHONE_UID;
 import static android.os.Process.PROC_OUT_LONG;
 import static android.os.Process.PROC_SPACE_TERM;
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SCHED_FIFO;
 import static android.os.Process.SCHED_RESET_ON_FORK;
-import static android.os.Process.SE_UID;
 import static android.os.Process.SHELL_UID;
 import static android.os.Process.SIGNAL_USR1;
 import static android.os.Process.SYSTEM_UID;
@@ -145,8 +141,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
@@ -155,7 +149,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -265,10 +258,7 @@
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
-import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -316,9 +306,6 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
-import android.media.audiofx.AudioEffect;
-import android.net.ConnectivityManager;
-import android.net.Proxy;
 import android.net.Uri;
 import android.os.AppZygote;
 import android.os.BatteryStats;
@@ -374,9 +361,7 @@
 import android.server.ServerProtoEnums;
 import android.system.Os;
 import android.system.OsConstants;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -386,7 +371,6 @@
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Pair;
-import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -435,11 +419,11 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
+import com.android.server.crashrecovery.CrashRecoveryHelper;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.BootReceiver;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.DisplayThread;
-import com.android.server.IntentResolver;
 import com.android.server.IoThread;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
@@ -451,7 +435,6 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
 import com.android.server.Watchdog;
-import com.android.server.am.ComponentAliasResolver.Resolution;
 import com.android.server.am.LowMemDetector.MemFactor;
 import com.android.server.appop.AppOpsService;
 import com.android.server.compat.PlatformCompat;
@@ -462,14 +445,12 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.net.NetworkManagementInternal;
 import com.android.server.os.NativeTombstoneManager;
-import com.android.server.pm.Computer;
 import com.android.server.pm.Installer;
 import com.android.server.pm.SaferIntentUtils;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.SELinuxUtil;
-import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.sdksandbox.SdkSandboxManagerLocal;
 import com.android.server.stats.pull.StatsPullAtomService;
@@ -512,7 +493,6 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -537,7 +517,6 @@
 
     static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
     static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
-    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
     private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
@@ -565,9 +544,6 @@
 
     static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed";
 
-    // Maximum number of receivers an app can register.
-    private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
-
     // How long we wait for a launched process to attach to the activity manager
     // before we decide it's never going to come up for real.
     static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
@@ -652,15 +628,6 @@
     static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
     static final String EXTRA_EXTRA_ATTACHMENT_URI =
             "android.intent.extra.EXTRA_ATTACHMENT_URI";
-    /**
-     * It is now required for apps to explicitly set either
-     * {@link android.content.Context#RECEIVER_EXPORTED} or
-     * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
-     * unprotected broadcast in code.
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
 
     /**
      * The maximum number of bytes that {@link #setProcessStateSummary} accepts.
@@ -737,11 +704,9 @@
     // so that dispatch of foreground broadcasts gets precedence.
     private BroadcastQueue mBroadcastQueue;
 
-    @GuardedBy("this")
-    BroadcastStats mLastBroadcastStats;
-
-    @GuardedBy("this")
-    BroadcastStats mCurBroadcastStats;
+    // TODO: Add a consistent way of accessing the methods within this class. Currently, some
+    // methods require access while holding a lock, while others do not.
+    BroadcastController mBroadcastController;
 
     TraceErrorLogger mTraceErrorLogger;
 
@@ -763,6 +728,7 @@
 
     final AppErrors mAppErrors;
     final PackageWatchdog mPackageWatchdog;
+    final CrashRecoveryHelper mCrashRecoveryHelper;
 
     @GuardedBy("mDeliveryGroupPolicyIgnoredActions")
     private final ArraySet<String> mDeliveryGroupPolicyIgnoredActions = new ArraySet();
@@ -860,12 +826,6 @@
     };
 
     /**
-     * Broadcast actions that will always be deliverable to unlaunched/background apps
-     */
-    @GuardedBy("this")
-    private ArraySet<String> mBackgroundLaunchBroadcasts;
-
-    /**
      * When an app has restrictions on the other apps that can have associations with it,
      * it appears here with a set of the allowed apps and also track debuggability of the app.
      */
@@ -1133,97 +1093,6 @@
     private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>();
     private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
 
-    /**
-     * Keeps track of all IIntentReceivers that have been registered for broadcasts.
-     * Hash keys are the receiver IBinder, hash value is a ReceiverList.
-     */
-    @GuardedBy("this")
-    final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
-
-    /**
-     * Resolver for broadcast intents to registered receivers.
-     * Holds BroadcastFilter (subclass of IntentFilter).
-     */
-    final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
-            = new IntentResolver<BroadcastFilter, BroadcastFilter>() {
-        @Override
-        protected boolean allowFilterResult(
-                BroadcastFilter filter, List<BroadcastFilter> dest) {
-            IBinder target = filter.receiverList.receiver.asBinder();
-            for (int i = dest.size() - 1; i >= 0; i--) {
-                if (dest.get(i).receiverList.receiver.asBinder() == target) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
-                int match, int userId, long customFlags) {
-            if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
-                    || userId == filter.owningUserId) {
-                return super.newResult(computer, filter, match, userId, customFlags);
-            }
-            return null;
-        }
-
-        @Override
-        protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
-            return input;
-        }
-
-        @Override
-        protected BroadcastFilter[] newArray(int size) {
-            return new BroadcastFilter[size];
-        }
-
-        @Override
-        protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
-            return packageName.equals(filter.packageName);
-        }
-    };
-
-    /**
-     * State of all active sticky broadcasts per user.  Keys are the action of the
-     * sticky Intent, values are an ArrayList of all broadcasted intents with
-     * that action (which should usually be one).  The SparseArray is keyed
-     * by the user ID the sticky is for, and can include UserHandle.USER_ALL
-     * for stickies that are sent to all users.
-     */
-    @GuardedBy("mStickyBroadcasts")
-    final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
-            new SparseArray<>();
-
-    @VisibleForTesting
-    static final class StickyBroadcast {
-        public Intent intent;
-        public boolean deferUntilActive;
-        public int originalCallingUid;
-        /** The snapshot process state of the app who sent this broadcast */
-        public int originalCallingAppProcessState;
-        public String resolvedDataType;
-
-        public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
-                int originalCallingUid, int originalCallingAppProcessState,
-                String resolvedDataType) {
-            final StickyBroadcast b = new StickyBroadcast();
-            b.intent = intent;
-            b.deferUntilActive = deferUntilActive;
-            b.originalCallingUid = originalCallingUid;
-            b.originalCallingAppProcessState = originalCallingAppProcessState;
-            b.resolvedDataType = resolvedDataType;
-            return b;
-        }
-
-        @Override
-        public String toString() {
-            return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
-                    + originalCallingUid + ", originalCallingAppProcessState="
-                    + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
-        }
-    }
-
     final ActiveServices mServices;
 
     final static class Association {
@@ -1685,7 +1554,7 @@
     // Encapsulates the global setting "hidden_api_blacklist_exemptions"
     final HiddenApiSettings mHiddenApiBlacklist;
 
-    private final PlatformCompat mPlatformCompat;
+    final PlatformCompat mPlatformCompat;
 
     PackageManagerInternal mPackageManagerInt;
     PermissionManagerServiceInternal mPermissionManagerInt;
@@ -2326,10 +2195,12 @@
                 mService.mBatteryStatsService.systemServicesReady();
                 mService.mServices.systemServicesReady();
             } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
-                mService.startBroadcastObservers();
+                mService.mBroadcastController.startBroadcastObservers();
             } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
                 if (!refactorCrashrecovery()) {
                     mService.mPackageWatchdog.onPackagesReady();
+                } else {
+                    mService.mCrashRecoveryHelper.registerConnectivityModuleHealthListener();
                 }
                 mService.scheduleHomeTimeout();
             }
@@ -2500,6 +2371,7 @@
         mUiContext = null;
         mAppErrors = injector.getAppErrors();
         mPackageWatchdog = null;
+        mCrashRecoveryHelper = null;
         mAppOpsService = mInjector.getAppOpsService(null /* recentAccessesFile */,
             null /* storageFile */, null /* handler */);
         mBatteryStatsService = mInjector.getBatteryStatsService();
@@ -2537,6 +2409,7 @@
         mPendingStartActivityUids = new PendingStartActivityUids();
         mUseFifoUiScheduling = false;
         mBroadcastQueue = injector.getBroadcastQueue(this);
+        mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
         mComponentAliasResolver = new ComponentAliasResolver(this);
     }
 
@@ -2579,9 +2452,11 @@
                 : new OomAdjuster(this, mProcessList, activeUids);
 
         mBroadcastQueue = mInjector.getBroadcastQueue(this);
+        mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
 
         mServices = new ActiveServices(this);
         mCpHelper = new ContentProviderHelper(this, true);
+        mCrashRecoveryHelper = new CrashRecoveryHelper(mUiContext);
         mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
         mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);
         mUidObserverController = new UidObserverController(mUiHandler);
@@ -2646,6 +2521,7 @@
 
     void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
         mBroadcastQueue = broadcastQueue;
+        mBroadcastController.setBroadcastQueueForTest(broadcastQueue);
     }
 
     BroadcastQueue getBroadcastQueue() {
@@ -2680,18 +2556,6 @@
         mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
     }
 
-    private ArraySet<String> getBackgroundLaunchBroadcasts() {
-        if (mBackgroundLaunchBroadcasts == null) {
-            mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
-        }
-        return mBackgroundLaunchBroadcasts;
-    }
-
-    private String getWearRemoteIntentAction() {
-        return mContext.getResources().getString(
-                    com.android.internal.R.string.config_wearRemoteIntentAction);
-    }
-
     /**
      * Ensures that the given package name has an explicit set of allowed associations.
      * If it does not, give it an empty set.
@@ -2762,7 +2626,7 @@
     }
 
     /** Updates allowed associations for app info (specifically, based on debuggability).  */
-    private void updateAssociationForApp(ApplicationInfo appInfo) {
+    void updateAssociationForApp(ApplicationInfo appInfo) {
         ensureAllowedAssociations();
         PackageAssociationInfo pai = mAllowedAssociations.get(appInfo.packageName);
         if (pai != null) {
@@ -3888,7 +3752,7 @@
         forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED, null);
     }
 
-    private void forceStopPackage(final String packageName, int userId, int userRunningFlags,
+    void forceStopPackage(final String packageName, int userId, int userRunningFlags,
             String reason) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -4216,7 +4080,7 @@
         mPackageManagerInt.sendPackageRestartedBroadcast(packageName, uid, flags);
     }
 
-    private void cleanupDisabledPackageComponentsLocked(
+    void cleanupDisabledPackageComponentsLocked(
             String packageName, int userId, String[] changedClasses) {
 
         Set<String> disabledClasses = null;
@@ -4454,9 +4318,7 @@
 
         if (packageName == null) {
             // Remove all sticky broadcasts from this user.
-            synchronized (mStickyBroadcasts) {
-                mStickyBroadcasts.remove(userId);
-            }
+            mBroadcastController.removeStickyBroadcasts(userId);
         }
 
         ArrayList<ContentProviderRecord> providers = new ArrayList<>();
@@ -5564,7 +5426,9 @@
             for (int i=0; i<intents.length; i++) {
                 Intent intent = intents[i];
                 if (intent != null) {
-                    intent.prepareToEnterSystemServer();
+                    if (intent.hasFileDescriptors()) {
+                        throw new IllegalArgumentException("File descriptors passed in Intent");
+                    }
                     if (type == ActivityManager.INTENT_SENDER_BROADCAST &&
                             (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
                         throw new IllegalArgumentException(
@@ -5597,6 +5461,7 @@
                         }
                     }
                     intents[i] = new Intent(intent);
+                    intents[i].removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
                 }
             }
             if (resolvedTypes != null && resolvedTypes.length != intents.length) {
@@ -9329,10 +9194,6 @@
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
     }
 
-    private void startBroadcastObservers() {
-        mBroadcastQueue.start(mContext.getContentResolver());
-    }
-
     private void updateForceBackgroundCheck(boolean enabled) {
         synchronized (this) {
             synchronized (mProcLock) {
@@ -10524,14 +10385,15 @@
                 pw.println(
                         "-------------------------------------------------------------------------------");
             }
-            dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+            mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
             pw.println();
             if (dumpAll) {
                 pw.println(
                         "-------------------------------------------------------------------------------");
             }
             if (dumpAll || dumpPackage != null) {
-                dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll,
+                        dumpPackage);
                 pw.println();
                 if (dumpAll) {
                     pw.println(
@@ -10782,7 +10644,7 @@
             } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
                 // output proto is ActivityManagerServiceDumpBroadcastsProto
                 synchronized (this) {
-                    writeBroadcastsToProtoLocked(proto);
+                    mBroadcastController.writeBroadcastsToProtoLocked(proto);
                 }
             } else if ("provider".equals(cmd)) {
                 String[] newArgs;
@@ -10846,7 +10708,7 @@
                     proto.end(activityToken);
 
                     long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
-                    writeBroadcastsToProtoLocked(proto);
+                    mBroadcastController.writeBroadcastsToProtoLocked(proto);
                     proto.end(broadcastToken);
 
                     long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
@@ -10906,7 +10768,8 @@
                     opti++;
                 }
                 synchronized (this) {
-                    dumpBroadcastsLocked(fd, pw, args, opti, /* dumpAll= */ true, dumpPackage);
+                    mBroadcastController.dumpBroadcastsLocked(fd, pw, args, opti,
+                            /* dumpAll= */ true, dumpPackage);
                 }
             } else if ("broadcast-stats".equals(cmd)) {
                 if (opti < args.length) {
@@ -10915,10 +10778,11 @@
                 }
                 synchronized (this) {
                     if (dumpCheckinFormat) {
-                        dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
-                                dumpPackage);
+                        mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti,
+                                dumpCheckin, dumpPackage);
                     } else {
-                        dumpBroadcastStatsLocked(fd, pw, args, opti, true, dumpPackage);
+                        mBroadcastController.dumpBroadcastStatsLocked(fd, pw, args, opti, true,
+                                dumpPackage);
                     }
                 }
             } else if ("intents".equals(cmd) || "i".equals(cmd)) {
@@ -11072,7 +10936,8 @@
 
         // No piece of data specified, dump everything.
         if (dumpCheckinFormat) {
-            dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin, dumpPackage);
+            mBroadcastController.dumpBroadcastStatsCheckinLocked(fd, pw, args, opti, dumpCheckin,
+                    dumpPackage);
         } else {
             if (dumpClient) {
                 // dumpEverything() will take the lock when needed, and momentarily drop
@@ -11783,42 +11648,6 @@
         }
     }
 
-    void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
-        if (mRegisteredReceivers.size() > 0) {
-            Iterator it = mRegisteredReceivers.values().iterator();
-            while (it.hasNext()) {
-                ReceiverList r = (ReceiverList)it.next();
-                r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
-            }
-        }
-        mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
-        mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
-        synchronized (mStickyBroadcasts) {
-            for (int user = 0; user < mStickyBroadcasts.size(); user++) {
-                long token = proto.start(
-                        ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
-                proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
-                for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
-                        : mStickyBroadcasts.valueAt(user).entrySet()) {
-                    long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
-                    proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
-                    for (StickyBroadcast broadcast : ent.getValue()) {
-                        broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
-                                false, true, true, false);
-                    }
-                    proto.end(actionToken);
-                }
-                proto.end(token);
-            }
-        }
-
-        long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
-        proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER, mHandler.toString());
-        mHandler.getLooper().dumpDebug(proto,
-            ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
-        proto.end(handlerToken);
-    }
-
     void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         pw.println(
@@ -11854,219 +11683,6 @@
         }
     }
 
-    @NeverCompile
-    void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
-        boolean dumpConstants = true;
-        boolean dumpHistory = true;
-        boolean needSep = false;
-        boolean onlyHistory = false;
-        boolean printedAnything = false;
-        boolean onlyReceivers = false;
-        int filteredUid = Process.INVALID_UID;
-
-        if ("history".equals(dumpPackage)) {
-            if (opti < args.length && "-s".equals(args[opti])) {
-                dumpAll = false;
-            }
-            onlyHistory = true;
-            dumpPackage = null;
-        }
-        if ("receivers".equals(dumpPackage)) {
-            onlyReceivers = true;
-            dumpPackage = null;
-            if (opti + 2 <= args.length) {
-                for (int i = opti; i < args.length; i++) {
-                    String arg = args[i];
-                    switch (arg) {
-                        case "--uid":
-                            filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
-                            if (filteredUid == Process.INVALID_UID) {
-                                return;
-                            }
-                            break;
-                        default:
-                            pw.printf("Invalid argument at index %d: %s\n", i, arg);
-                            return;
-                    }
-                }
-            }
-        }
-        if (DEBUG_BROADCAST) {
-            Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
-                    + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory, onlyReceivers,
-                    filteredUid);
-        }
-
-        pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
-        if (!onlyHistory && dumpAll) {
-            if (mRegisteredReceivers.size() > 0) {
-                boolean printed = false;
-                Iterator it = mRegisteredReceivers.values().iterator();
-                while (it.hasNext()) {
-                    ReceiverList r = (ReceiverList)it.next();
-                    if (dumpPackage != null && (r.app == null ||
-                            !dumpPackage.equals(r.app.info.packageName))) {
-                        continue;
-                    }
-                    if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
-                        if (DEBUG_BROADCAST) {
-                            Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
-                                    + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
-                        }
-                        continue;
-                    }
-                    if (!printed) {
-                        pw.println("  Registered Receivers:");
-                        needSep = true;
-                        printed = true;
-                        printedAnything = true;
-                    }
-                    pw.print("  * "); pw.println(r);
-                    r.dump(pw, "    ");
-                }
-            } else {
-                if (onlyReceivers) {
-                    pw.println("  (no registered receivers)");
-                }
-            }
-
-            if (!onlyReceivers) {
-                if (mReceiverResolver.dump(pw, needSep
-                        ? "\n  Receiver Resolver Table:" : "  Receiver Resolver Table:",
-                        "    ", dumpPackage, false, false)) {
-                    needSep = true;
-                    printedAnything = true;
-                }
-            }
-        }
-
-        if (!onlyReceivers) {
-            needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
-                    dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
-            printedAnything |= needSep;
-        }
-
-        needSep = true;
-
-        synchronized (mStickyBroadcasts) {
-            if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
-                    && dumpPackage == null) {
-                for (int user = 0; user < mStickyBroadcasts.size(); user++) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    printedAnything = true;
-                    pw.print("  Sticky broadcasts for user ");
-                    pw.print(mStickyBroadcasts.keyAt(user));
-                    pw.println(":");
-                    StringBuilder sb = new StringBuilder(128);
-                    for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
-                            : mStickyBroadcasts.valueAt(user).entrySet()) {
-                        pw.print("  * Sticky action ");
-                        pw.print(ent.getKey());
-                        if (dumpAll) {
-                            pw.println(":");
-                            ArrayList<StickyBroadcast> broadcasts = ent.getValue();
-                            final int N = broadcasts.size();
-                            for (int i = 0; i < N; i++) {
-                                final Intent intent = broadcasts.get(i).intent;
-                                final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
-                                sb.setLength(0);
-                                sb.append("    Intent: ");
-                                intent.toShortString(sb, false, true, false, false);
-                                pw.print(sb);
-                                if (deferUntilActive) {
-                                    pw.print(" [D]");
-                                }
-                                pw.println();
-                                pw.print("      originalCallingUid: ");
-                                pw.println(broadcasts.get(i).originalCallingUid);
-                                pw.println();
-                                Bundle bundle = intent.getExtras();
-                                if (bundle != null) {
-                                    pw.print("      extras: ");
-                                    pw.println(bundle);
-                                }
-                            }
-                        } else {
-                            pw.println("");
-                        }
-                    }
-                }
-            }
-        }
-
-        if (!onlyHistory && !onlyReceivers && dumpAll) {
-            pw.println();
-            pw.println("  Queue " + mBroadcastQueue.toString() + ": "
-                    + mBroadcastQueue.describeStateLocked());
-            pw.println("  mHandler:");
-            mHandler.dump(new PrintWriterPrinter(pw), "    ");
-            needSep = true;
-            printedAnything = true;
-        }
-
-        if (!printedAnything) {
-            pw.println("  (nothing)");
-        }
-    }
-
-    @NeverCompile
-    void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
-        if (mCurBroadcastStats == null) {
-            return;
-        }
-
-        pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
-        final long now = SystemClock.elapsedRealtime();
-        if (mLastBroadcastStats != null) {
-            pw.print("  Last stats (from ");
-            TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
-            pw.print(" to ");
-            TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
-            pw.print(", ");
-            TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
-                    - mLastBroadcastStats.mStartUptime, pw);
-            pw.println(" uptime):");
-            if (!mLastBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
-                pw.println("    (nothing)");
-            }
-            pw.println();
-        }
-        pw.print("  Current stats (from ");
-        TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
-        pw.print(" to now, ");
-        TimeUtils.formatDuration(SystemClock.uptimeMillis()
-                - mCurBroadcastStats.mStartUptime, pw);
-        pw.println(" uptime):");
-        if (!mCurBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
-            pw.println("    (nothing)");
-        }
-    }
-
-    @NeverCompile
-    void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean fullCheckin, String dumpPackage) {
-        if (mCurBroadcastStats == null) {
-            return;
-        }
-
-        if (mLastBroadcastStats != null) {
-            mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
-            if (fullCheckin) {
-                mLastBroadcastStats = null;
-                return;
-            }
-        }
-        mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
-        if (fullCheckin) {
-            mCurBroadcastStats = null;
-        }
-    }
-
     void dumpPermissions(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
 
@@ -12545,7 +12161,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.");
@@ -12555,10 +12171,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;
@@ -13975,7 +13594,12 @@
         enforceNotIsolatedCaller("startService");
         enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
         if (service != null) {
-            service.prepareToEnterSystemServer();
+            // Refuse possible leaked file descriptors
+            if (service.hasFileDescriptors()) {
+                throw new IllegalArgumentException("File descriptors passed in Intent");
+            }
+            // Remove existing mismatch flag so it can be properly updated later
+            service.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
         }
 
         if (callingPackage == null) {
@@ -14212,7 +13836,12 @@
         enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
 
         if (service != null) {
-            service.prepareToEnterSystemServer();
+            // Refuse possible leaked file descriptors
+            if (service.hasFileDescriptors()) {
+                throw new IllegalArgumentException("File descriptors passed in Intent");
+            }
+            // Remove existing mismatch flag so it can be properly updated later
+            service.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
         }
 
         if (callingPackage == null) {
@@ -14588,33 +14217,6 @@
     // BROADCASTS
     // =========================================================
 
-    private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
-        if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
-            return false;
-        }
-        // Easy case -- we have the app's ProcessRecord.
-        if (record != null) {
-            return record.info.isInstantApp();
-        }
-        // Otherwise check with PackageManager.
-        IPackageManager pm = AppGlobals.getPackageManager();
-        try {
-            if (callerPackage == null) {
-                final String[] packageNames = pm.getPackagesForUid(uid);
-                if (packageNames == null || packageNames.length == 0) {
-                    throw new IllegalArgumentException("Unable to determine caller package name");
-                }
-                // Instant Apps can't use shared uids, so its safe to only check the first package.
-                callerPackage = packageNames[0];
-            }
-            mAppOpsService.checkPackage(uid, callerPackage);
-            return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
-            return true;
-        }
-    }
-
     /**
      * @deprecated Use {@link #registerReceiverWithFeature}
      */
@@ -14629,657 +14231,12 @@
     public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
             String callerFeatureId, String receiverId, IIntentReceiver receiver,
             IntentFilter filter, String permission, int userId, int flags) {
-        traceRegistrationBegin(receiverId, receiver, filter, userId);
-        try {
-            return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
-                    receiverId, receiver, filter, permission, userId, flags);
-        } finally {
-            traceRegistrationEnd();
-        }
-    }
-
-    private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
-            IntentFilter filter, int userId) {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            final StringBuilder sb = new StringBuilder("registerReceiver: ");
-            sb.append(Binder.getCallingUid()); sb.append('/');
-            sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
-            final int actionsCount = filter.safeCountActions();
-            if (actionsCount > 0) {
-                for (int i = 0; i < actionsCount; ++i) {
-                    sb.append(filter.getAction(i));
-                    if (i != actionsCount - 1) sb.append(',');
-                }
-            } else {
-                sb.append("null");
-            }
-            sb.append('/');
-            sb.append('u'); sb.append(userId); sb.append('/');
-            sb.append(receiver == null ? "null" : receiver.asBinder());
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
-        }
-    }
-
-    private static void traceRegistrationEnd() {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-    }
-
-    private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
-            String callerPackage, String callerFeatureId, String receiverId,
-            IIntentReceiver receiver, IntentFilter filter, String permission,
-            int userId, int flags) {
-        enforceNotIsolatedCaller("registerReceiver");
-        ArrayList<StickyBroadcast> stickyBroadcasts = null;
-        ProcessRecord callerApp = null;
-        final boolean visibleToInstantApps
-                = (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
-
-        int callingUid;
-        int callingPid;
-        boolean instantApp;
-        synchronized (mProcLock) {
-            callerApp = getRecordForAppLOSP(caller);
-            if (callerApp == null) {
-                Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
-                return null;
-            }
-            if (callerApp.info.uid != SYSTEM_UID
-                    && !callerApp.getPkgList().containsKey(callerPackage)
-                    && !"android".equals(callerPackage)) {
-                throw new SecurityException("Given caller package " + callerPackage
-                        + " is not running in process " + callerApp);
-            }
-            callingUid = callerApp.info.uid;
-            callingPid = callerApp.getPid();
-
-            instantApp = isInstantApp(callerApp, callerPackage, callingUid);
-        }
-        userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
-                ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
-
-        // Warn if system internals are registering for important broadcasts
-        // without also using a priority to ensure they process the event
-        // before normal apps hear about it
-        if (UserHandle.isCore(callingUid)) {
-            final int priority = filter.getPriority();
-            final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
-                    || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
-            if (!systemPriority) {
-                final int N = filter.countActions();
-                for (int i = 0; i < N; i++) {
-                    // TODO: expand to additional important broadcasts over time
-                    final String action = filter.getAction(i);
-                    if (action.startsWith("android.intent.action.USER_")
-                            || action.startsWith("android.intent.action.PACKAGE_")
-                            || action.startsWith("android.intent.action.UID_")
-                            || action.startsWith("android.intent.action.EXTERNAL_")
-                            || action.startsWith("android.bluetooth.")
-                            || action.equals(Intent.ACTION_SHUTDOWN)) {
-                        if (DEBUG_BROADCAST) {
-                            Slog.wtf(TAG,
-                                    "System internals registering for " + filter.toLongString()
-                                            + " with app priority; this will race with apps!",
-                                    new Throwable());
-                        }
-
-                        // When undefined, assume that system internals need
-                        // to hear about the event first; they can use
-                        // SYSTEM_LOW_PRIORITY if they need to hear last
-                        if (priority == 0) {
-                            filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-                        }
-                        break;
-                    }
-                }
-            }
-        }
-
-        Iterator<String> actions = filter.actionsIterator();
-        if (actions == null) {
-            ArrayList<String> noAction = new ArrayList<String>(1);
-            noAction.add(null);
-            actions = noAction.iterator();
-        }
-        boolean onlyProtectedBroadcasts = true;
-
-        // Collect stickies of users and check if broadcast is only registered for protected
-        // broadcasts
-        int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
-        synchronized (mStickyBroadcasts) {
-            while (actions.hasNext()) {
-                String action = actions.next();
-                for (int id : userIds) {
-                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
-                            mStickyBroadcasts.get(id);
-                    if (stickies != null) {
-                        ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
-                        if (broadcasts != null) {
-                            if (stickyBroadcasts == null) {
-                                stickyBroadcasts = new ArrayList<>();
-                            }
-                            stickyBroadcasts.addAll(broadcasts);
-                        }
-                    }
-                }
-                if (onlyProtectedBroadcasts) {
-                    try {
-                        onlyProtectedBroadcasts &=
-                                AppGlobals.getPackageManager().isProtectedBroadcast(action);
-                    } catch (RemoteException e) {
-                        onlyProtectedBroadcasts = false;
-                        Slog.w(TAG, "Remote exception", e);
-                    }
-                }
-            }
-        }
-
-        if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
-            SdkSandboxManagerLocal sdkSandboxManagerLocal =
-                    LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
-            if (sdkSandboxManagerLocal == null) {
-                throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
-                        + " whether SDK sandbox uid can register to broadcast receivers.");
-            }
-            if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
-                    /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
-                throw new SecurityException("SDK sandbox not allowed to register receiver"
-                        + " with the given IntentFilter: " + filter.toLongString());
-            }
-        }
-
-        // If the change is enabled, but neither exported or not exported is set, we need to log
-        // an error so the consumer can know to explicitly set the value for their flag.
-        // If the caller is registering for a sticky broadcast with a null receiver, we won't
-        // require a flag
-        final boolean explicitExportStateDefined =
-                (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
-        if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
-                (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
-            throw new IllegalArgumentException(
-                    "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
-                            + "flag");
-        }
-
-        // Don't enforce the flag check if we're EITHER registering for only protected
-        // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
-        // not be used generally, so we will be marking them as exported by default
-        boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
-                DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
-
-        // A receiver that is visible to instant apps must also be exported.
-        final boolean unexportedReceiverVisibleToInstantApps =
-                ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
-                        (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
-        if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
-            throw new IllegalArgumentException(
-                    "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
-                            + "RECEIVER_NOT_EXPORTED flag");
-        }
-
-        if (!onlyProtectedBroadcasts) {
-            if (receiver == null && !explicitExportStateDefined) {
-                // sticky broadcast, no flag specified (flag isn't required)
-                flags |= Context.RECEIVER_EXPORTED;
-            } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
-                throw new SecurityException(
-                        callerPackage + ": One of RECEIVER_EXPORTED or "
-                                + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
-                                + "isn't being registered exclusively for system broadcasts");
-                // Assume default behavior-- flag check is not enforced
-            } else if (!requireExplicitFlagForDynamicReceivers && (
-                    (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
-                // Change is not enabled, assume exported unless otherwise specified.
-                flags |= Context.RECEIVER_EXPORTED;
-            }
-        } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
-            flags |= Context.RECEIVER_EXPORTED;
-        }
-
-        // Dynamic receivers are exported by default for versions prior to T
-        final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
-
-        ArrayList<StickyBroadcast> allSticky = null;
-        if (stickyBroadcasts != null) {
-            final ContentResolver resolver = mContext.getContentResolver();
-            // Look for any matching sticky broadcasts...
-            for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
-                final StickyBroadcast broadcast = stickyBroadcasts.get(i);
-                Intent intent = broadcast.intent;
-                // Don't provided intents that aren't available to instant apps.
-                if (instantApp &&
-                        (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) {
-                    continue;
-                }
-                // If intent has scheme "content", it will need to access
-                // provider that needs to lock mProviderMap in ActivityThread
-                // and also it may need to wait application response, so we
-                // cannot lock ActivityManagerService here.
-                final int match;
-                if (Flags.avoidResolvingType()) {
-                    match = filter.match(intent.getAction(), broadcast.resolvedDataType,
-                        intent.getScheme(), intent.getData(), intent.getCategories(),
-                        TAG, false /* supportsWildcards */, null /* ignoreActions */,
-                        intent.getExtras());
-                } else {
-                    match = filter.match(resolver, intent, true, TAG);
-                }
-                if (match >= 0) {
-                    if (allSticky == null) {
-                        allSticky = new ArrayList<>();
-                    }
-                    allSticky.add(broadcast);
-                }
-            }
-        }
-
-        // The first sticky in the list is returned directly back to the client.
-        Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
-        if (receiver == null) {
-            return sticky;
-        }
-
-        // SafetyNet logging for b/177931370. If any process other than system_server tries to
-        // listen to this broadcast action, then log it.
-        if (callingPid != Process.myPid()) {
-            if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
-                    || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
-                EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
-            }
-        }
-
-        synchronized (this) {
-            IApplicationThread thread;
-            if (callerApp != null && ((thread = callerApp.getThread()) == null
-                    || thread.asBinder() != caller.asBinder())) {
-                // Original caller already died
-                return null;
-            }
-            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
-            if (rl == null) {
-                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
-                        userId, receiver);
-                if (rl.app != null) {
-                    final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
-                    if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
-                        throw new IllegalStateException("Too many receivers, total of "
-                                + totalReceiversForApp + ", registered for pid: "
-                                + rl.pid + ", callerPackage: " + callerPackage);
-                    }
-                    rl.app.mReceivers.addReceiver(rl);
-                } else {
-                    try {
-                        receiver.asBinder().linkToDeath(rl, 0);
-                    } catch (RemoteException e) {
-                        return sticky;
-                    }
-                    rl.linkedToDeath = true;
-                }
-                mRegisteredReceivers.put(receiver.asBinder(), rl);
-            } else if (rl.uid != callingUid) {
-                throw new IllegalArgumentException(
-                        "Receiver requested to register for uid " + callingUid
-                        + " was previously registered for uid " + rl.uid
-                        + " callerPackage is " + callerPackage);
-            } else if (rl.pid != callingPid) {
-                throw new IllegalArgumentException(
-                        "Receiver requested to register for pid " + callingPid
-                        + " was previously registered for pid " + rl.pid
-                        + " callerPackage is " + callerPackage);
-            } else if (rl.userId != userId) {
-                throw new IllegalArgumentException(
-                        "Receiver requested to register for user " + userId
-                        + " was previously registered for user " + rl.userId
-                        + " callerPackage is " + callerPackage);
-            }
-            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
-                    receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
-                    exported);
-            if (rl.containsFilter(filter)) {
-                Slog.w(TAG, "Receiver with filter " + filter
-                        + " already registered for pid " + rl.pid
-                        + ", callerPackage is " + callerPackage);
-            } else {
-                rl.add(bf);
-                if (!bf.debugCheck()) {
-                    Slog.w(TAG, "==> For Dynamic broadcast");
-                }
-                mReceiverResolver.addFilter(getPackageManagerInternal().snapshot(), bf);
-            }
-
-            // Enqueue broadcasts for all existing stickies that match
-            // this filter.
-            if (allSticky != null) {
-                ArrayList receivers = new ArrayList();
-                receivers.add(bf);
-                sticky = null;
-
-                final int stickyCount = allSticky.size();
-                for (int i = 0; i < stickyCount; i++) {
-                    final StickyBroadcast broadcast = allSticky.get(i);
-                    final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
-                    // TODO(b/281889567): consider using checkComponentPermission instead of
-                    //  canAccessUnexportedComponents
-                    if (sticky == null && (exported || originalStickyCallingUid == callingUid
-                            || ActivityManager.canAccessUnexportedComponents(
-                            originalStickyCallingUid))) {
-                        sticky = broadcast.intent;
-                    }
-                    BroadcastQueue queue = mBroadcastQueue;
-                    BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
-                            null, null, -1, -1, false, null, null, null, null, OP_NONE,
-                            BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
-                            receivers, null, null, 0, null, null, false, true, true, -1,
-                            originalStickyCallingUid, BackgroundStartPrivileges.NONE,
-                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
-                            null /* filterExtrasForReceiver */,
-                            broadcast.originalCallingAppProcessState);
-                    queue.enqueueBroadcastLocked(r);
-                }
-            }
-
-            return sticky;
-        }
+        return mBroadcastController.registerReceiverWithFeature(caller, callerPackage,
+                callerFeatureId, receiverId, receiver, filter, permission, userId, flags);
     }
 
     public void unregisterReceiver(IIntentReceiver receiver) {
-        traceUnregistrationBegin(receiver);
-        try {
-            unregisterReceiverTraced(receiver);
-        } finally {
-            traceUnregistrationEnd();
-        }
-    }
-
-    private static void traceUnregistrationBegin(IIntentReceiver receiver) {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
-                            receiver == null ? "null" : receiver.asBinder()));
-        }
-    }
-
-    private static void traceUnregistrationEnd() {
-        if (!Flags.traceReceiverRegistration()) {
-            return;
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
-    }
-
-    private void unregisterReceiverTraced(IIntentReceiver receiver) {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
-
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            boolean doTrim = false;
-            synchronized(this) {
-                ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
-                if (rl != null) {
-                    final BroadcastRecord r = rl.curBroadcast;
-                    if (r != null) {
-                        final boolean doNext = r.queue.finishReceiverLocked(
-                                rl.app, r.resultCode, r.resultData, r.resultExtras,
-                                r.resultAbort, false);
-                        if (doNext) {
-                            doTrim = true;
-                        }
-                    }
-                    if (rl.app != null) {
-                        rl.app.mReceivers.removeReceiver(rl);
-                    }
-                    removeReceiverLocked(rl);
-                    if (rl.linkedToDeath) {
-                        rl.linkedToDeath = false;
-                        rl.receiver.asBinder().unlinkToDeath(rl, 0);
-                    }
-                }
-
-                // If we actually concluded any broadcasts, we might now be able
-                // to trim the recipients' apps from our working set
-                if (doTrim) {
-                    trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
-                    return;
-                }
-            }
-
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    void removeReceiverLocked(ReceiverList rl) {
-        mRegisteredReceivers.remove(rl.receiver.asBinder());
-        for (int i = rl.size() - 1; i >= 0; i--) {
-            mReceiverResolver.removeFilter(rl.get(i));
-        }
-    }
-
-    private final void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
-        mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
-    }
-
-    private List<ResolveInfo> collectReceiverComponents(
-            Intent intent, String resolvedType, int callingUid, int callingPid,
-            int[] users, int[] broadcastAllowList) {
-        // TODO: come back and remove this assumption to triage all broadcasts
-        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
-
-        List<ResolveInfo> receivers = null;
-        HashSet<ComponentName> singleUserReceivers = null;
-        boolean scannedFirstReceivers = false;
-        for (int user : users) {
-            // Skip users that have Shell restrictions
-            if (callingUid == SHELL_UID
-                    && mUserController.hasUserRestriction(
-                    UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
-                continue;
-            }
-            List<ResolveInfo> newReceivers = mPackageManagerInt.queryIntentReceivers(
-                    intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
-            if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
-                // If this is not the system user, we need to check for
-                // any receivers that should be filtered out.
-                for (int i = 0; i < newReceivers.size(); i++) {
-                    ResolveInfo ri = newReceivers.get(i);
-                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
-                        newReceivers.remove(i);
-                        i--;
-                    }
-                }
-            }
-            // Replace the alias receivers with their targets.
-            if (newReceivers != null) {
-                for (int i = newReceivers.size() - 1; i >= 0; i--) {
-                    final ResolveInfo ri = newReceivers.get(i);
-                    final Resolution<ResolveInfo> resolution =
-                            mComponentAliasResolver.resolveReceiver(intent, ri, resolvedType,
-                                    pmFlags, user, callingUid, callingPid);
-                    if (resolution == null) {
-                        // It was an alias, but the target was not found.
-                        newReceivers.remove(i);
-                        continue;
-                    }
-                    if (resolution.isAlias()) {
-                        newReceivers.set(i, resolution.getTarget());
-                    }
-                }
-            }
-            if (newReceivers != null && newReceivers.size() == 0) {
-                newReceivers = null;
-            }
-
-            if (receivers == null) {
-                receivers = newReceivers;
-            } else if (newReceivers != null) {
-                // We need to concatenate the additional receivers
-                // found with what we have do far.  This would be easy,
-                // but we also need to de-dup any receivers that are
-                // singleUser.
-                if (!scannedFirstReceivers) {
-                    // Collect any single user receivers we had already retrieved.
-                    scannedFirstReceivers = true;
-                    for (int i = 0; i < receivers.size(); i++) {
-                        ResolveInfo ri = receivers.get(i);
-                        if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
-                            ComponentName cn = new ComponentName(
-                                    ri.activityInfo.packageName, ri.activityInfo.name);
-                            if (singleUserReceivers == null) {
-                                singleUserReceivers = new HashSet<ComponentName>();
-                            }
-                            singleUserReceivers.add(cn);
-                        }
-                    }
-                }
-                // Add the new results to the existing results, tracking
-                // and de-dupping single user receivers.
-                for (int i = 0; i < newReceivers.size(); i++) {
-                    ResolveInfo ri = newReceivers.get(i);
-                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
-                        ComponentName cn = new ComponentName(
-                                ri.activityInfo.packageName, ri.activityInfo.name);
-                        if (singleUserReceivers == null) {
-                            singleUserReceivers = new HashSet<ComponentName>();
-                        }
-                        if (!singleUserReceivers.contains(cn)) {
-                            singleUserReceivers.add(cn);
-                            receivers.add(ri);
-                        }
-                    } else {
-                        receivers.add(ri);
-                    }
-                }
-            }
-        }
-        if (receivers != null && broadcastAllowList != null) {
-            for (int i = receivers.size() - 1; i >= 0; i--) {
-                final int receiverAppId = UserHandle.getAppId(
-                        receivers.get(i).activityInfo.applicationInfo.uid);
-                if (receiverAppId >= Process.FIRST_APPLICATION_UID
-                        && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
-                    receivers.remove(i);
-                }
-            }
-        }
-        return receivers;
-    }
-
-    private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
-            String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
-        if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
-            // Don't yell about broadcasts sent via shell
-            return;
-        }
-
-        final String action = intent.getAction();
-        if (isProtectedBroadcast
-                || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
-                || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
-                || Intent.ACTION_MEDIA_BUTTON.equals(action)
-                || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
-                || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
-                || Intent.ACTION_MASTER_CLEAR.equals(action)
-                || Intent.ACTION_FACTORY_RESET.equals(action)
-                || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
-                || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
-                || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
-                || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
-                || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
-                || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
-            // Broadcast is either protected, or it's a public action that
-            // we've relaxed, so it's fine for system internals to send.
-            return;
-        }
-
-        // This broadcast may be a problem...  but there are often system components that
-        // want to send an internal broadcast to themselves, which is annoying to have to
-        // explicitly list each action as a protected broadcast, so we will check for that
-        // one safe case and allow it: an explicit broadcast, only being received by something
-        // that has protected itself.
-        if (intent.getPackage() != null || intent.getComponent() != null) {
-            if (receivers == null || receivers.size() == 0) {
-                // Intent is explicit and there's no receivers.
-                // This happens, e.g. , when a system component sends a broadcast to
-                // its own runtime receiver, and there's no manifest receivers for it,
-                // because this method is called twice for each broadcast,
-                // for runtime receivers and manifest receivers and the later check would find
-                // no receivers.
-                return;
-            }
-            boolean allProtected = true;
-            for (int i = receivers.size()-1; i >= 0; i--) {
-                Object target = receivers.get(i);
-                if (target instanceof ResolveInfo) {
-                    ResolveInfo ri = (ResolveInfo)target;
-                    if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
-                        allProtected = false;
-                        break;
-                    }
-                } else {
-                    BroadcastFilter bf = (BroadcastFilter)target;
-                    if (bf.exported && bf.requiredPermission == null) {
-                        allProtected = false;
-                        break;
-                    }
-                }
-            }
-            if (allProtected) {
-                // All safe!
-                return;
-            }
-        }
-
-        // The vast majority of broadcasts sent from system internals
-        // should be protected to avoid security holes, so yell loudly
-        // to ensure we examine these cases.
-        if (callerApp != null) {
-            Log.wtf(TAG, "Sending non-protected broadcast " + action
-                            + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
-                    new Throwable());
-        } else {
-            Log.wtf(TAG, "Sending non-protected broadcast " + action
-                            + " from system uid " + UserHandle.formatUid(callingUid)
-                            + " pkg " + callerPackage,
-                    new Throwable());
-        }
-    }
-
-    // Apply permission policy around the use of specific broadcast options
-    void enforceBroadcastOptionPermissionsInternal(
-            @Nullable Bundle options, int callingUid) {
-        enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
-                callingUid);
-    }
-
-    void enforceBroadcastOptionPermissionsInternal(
-            @Nullable BroadcastOptions options, int callingUid) {
-        if (options != null && callingUid != Process.SYSTEM_UID) {
-            if (options.isAlarmBroadcast()) {
-                if (DEBUG_BROADCAST_LIGHT) {
-                    Slog.w(TAG, "Non-system caller " + callingUid
-                            + " may not flag broadcast as alarm");
-                }
-                throw new SecurityException(
-                        "Non-system callers may not flag broadcasts as alarm");
-            }
-            if (options.isInteractive()) {
-                enforceCallingPermission(
-                        android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
-                        "setInteractive");
-            }
-        }
+        mBroadcastController.unregisterReceiver(receiver);
     }
 
     @GuardedBy("this")
@@ -15290,1033 +14247,14 @@
             String[] excludedPackages, int appOp, Bundle bOptions, boolean ordered,
             boolean sticky, int callingPid,
             int callingUid, int realCallingUid, int realCallingPid, int userId) {
-        return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent,
-                resolvedType, null, resultTo, resultCode, resultData, resultExtras,
+        return mBroadcastController.broadcastIntentLocked(callerApp, callerPackage, callerFeatureId,
+                intent, resolvedType, null, resultTo, resultCode, resultData, resultExtras,
                 requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
                 ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
                 BackgroundStartPrivileges.NONE,
                 null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
     }
 
-    @GuardedBy("this")
-    final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
-            @Nullable String callerFeatureId, Intent intent, String resolvedType,
-            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
-            Bundle resultExtras, String[] requiredPermissions,
-            String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
-            boolean ordered, boolean sticky, int callingPid, int callingUid,
-            int realCallingUid, int realCallingPid, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges,
-            @Nullable int[] broadcastAllowList,
-            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
-        final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
-                callingUid, realCallingUid, userId);
-        try {
-            final BroadcastSentEventRecord broadcastSentEventRecord =
-                    new BroadcastSentEventRecord();
-            final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
-                    intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
-                    resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
-                    appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
-                    callingPid, callingUid, realCallingUid, realCallingPid, userId,
-                    backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
-                    broadcastSentEventRecord);
-            broadcastSentEventRecord.setResult(res);
-            broadcastSentEventRecord.logToStatsd();
-            return res;
-        } finally {
-            traceBroadcastIntentEnd(cookie);
-        }
-    }
-
-    private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
-            boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
-        if (!Flags.traceReceiverRegistration()) {
-            return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
-        }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            final StringBuilder sb = new StringBuilder("broadcastIntent: ");
-            sb.append(callingUid); sb.append('/');
-            final String action = intent.getAction();
-            sb.append(action == null ? null : action); sb.append('/');
-            sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
-            sb.append(ordered ? "O" : "_");
-            sb.append(sticky ? "S" : "_");
-            sb.append(resultTo != null ? "C" : "_");
-            sb.append('/');
-            sb.append('u'); sb.append(userId);
-            if (callingUid != realCallingUid) {
-                sb.append('/');
-                sb.append("sender="); sb.append(realCallingUid);
-            }
-            return BroadcastQueue.traceBegin(sb.toString());
-        }
-        return 0;
-    }
-
-    private static void traceBroadcastIntentEnd(int cookie) {
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-            BroadcastQueue.traceEnd(cookie);
-        }
-    }
-
-    @GuardedBy("this")
-    final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
-            @Nullable String callerFeatureId, Intent intent, String resolvedType,
-            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
-            Bundle resultExtras, String[] requiredPermissions,
-            String[] excludedPermissions, String[] excludedPackages, int appOp,
-            BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
-            int callingUid, int realCallingUid, int realCallingPid, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges,
-            @Nullable int[] broadcastAllowList,
-            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
-            @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
-        // Ensure all internal loopers are registered for idle checks
-        BroadcastLoopers.addMyLooper();
-
-        if (Process.isSdkSandboxUid(realCallingUid)) {
-            final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
-                    SdkSandboxManagerLocal.class);
-            if (sdkSandboxManagerLocal == null) {
-                throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
-                        + " a broadcast from an SDK sandbox uid.");
-            }
-            if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
-                throw new SecurityException(
-                        "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
-                        + " uid. Given caller package " + callerPackage + " (pid=" + callingPid
-                        + ", realCallingUid=" + realCallingUid + ", callingUid= " + callingUid
-                        + ")");
-            }
-        }
-
-        if ((resultTo != null) && (resultToApp == null)) {
-            if (resultTo.asBinder() instanceof BinderProxy) {
-                // Warn when requesting results without a way to deliver them
-                Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
-                        + " with resultTo requires resultToApp", new Throwable());
-            } else {
-                // If not a BinderProxy above, then resultTo is an in-process
-                // receiver, so splice in system_server process
-                resultToApp = getProcessRecordLocked("system", SYSTEM_UID);
-            }
-        }
-
-        intent = new Intent(intent);
-        broadcastSentEventRecord.setIntent(intent);
-        broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
-        broadcastSentEventRecord.setSenderUid(callingUid);
-        broadcastSentEventRecord.setRealSenderUid(realCallingUid);
-        broadcastSentEventRecord.setSticky(sticky);
-        broadcastSentEventRecord.setOrdered(ordered);
-        broadcastSentEventRecord.setResultRequested(resultTo != null);
-        final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
-        broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
-        broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
-                realCallingPid));
-
-        final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
-        // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
-        if (callerInstantApp) {
-            intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
-        }
-
-        if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
-                Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
-                        + "Assuming restrictive whitelist.");
-                broadcastAllowList = new int[]{};
-        }
-
-        // By default broadcasts do not go to stopped apps.
-        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
-
-        // If we have not finished booting, don't allow this to launch new processes.
-        if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        }
-
-        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
-                (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
-                        + " ordered=" + ordered + " userid=" + userId
-                        + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
-        if ((resultTo != null) && !ordered) {
-            if (!UserHandle.isCore(callingUid)) {
-                String msg = "Unauthorized unordered resultTo broadcast "
-                             + intent + " sent from uid " + callingUid;
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-            }
-        }
-
-        userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
-                ALLOW_NON_FULL, "broadcast", callerPackage);
-
-        // Make sure that the user who is receiving this broadcast or its parent is running.
-        // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
-        if (userId != UserHandle.USER_ALL && !mUserController.isUserOrItsParentRunning(userId)) {
-            if ((callingUid != SYSTEM_UID
-                    || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
-                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
-                Slog.w(TAG, "Skipping broadcast of " + intent
-                        + ": user " + userId + " and its parent (if any) are stopped");
-                scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
-                        brOptions, callingUid, callerPackage);
-                return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
-            }
-        }
-
-        final String action = intent.getAction();
-        if (brOptions != null) {
-            if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
-                // See if the caller is allowed to do this.  Note we are checking against
-                // the actual real caller (not whoever provided the operation as say a
-                // PendingIntent), because that who is actually supplied the arguments.
-                if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED
-                        && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED
-                        && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    String msg = "Permission Denial: " + intent.getAction()
-                            + " broadcast from " + callerPackage + " (pid=" + callingPid
-                            + ", uid=" + callingUid + ")"
-                            + " requires "
-                            + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
-                            + START_ACTIVITIES_FROM_BACKGROUND + " or "
-                            + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                }
-            }
-            if (brOptions.isDontSendToRestrictedApps()
-                    && !isUidActiveLOSP(callingUid)
-                    && isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
-                Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
-                        + " has background restrictions");
-                return ActivityManager.START_CANCELED;
-            }
-            if (brOptions.allowsBackgroundActivityStarts()) {
-                // See if the caller is allowed to do this.  Note we are checking against
-                // the actual real caller (not whoever provided the operation as say a
-                // PendingIntent), because that who is actually supplied the arguments.
-                if (checkComponentPermission(
-                        android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
-                        realCallingPid, realCallingUid, -1, true)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    String msg = "Permission Denial: " + intent.getAction()
-                            + " broadcast from " + callerPackage + " (pid=" + callingPid
-                            + ", uid=" + callingUid + ")"
-                            + " requires "
-                            + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                } else {
-                    // We set the token to null since if it wasn't for it we'd allow anyway here
-                    backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
-                }
-            }
-
-            if (brOptions.getIdForResponseEvent() > 0) {
-                enforcePermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
-                        callingPid, callingUid, "recordResponseEventWhileInBackground");
-            }
-        }
-
-        // Verify that protected broadcasts are only being sent by system code,
-        // and that system code is only sending protected broadcasts.
-        final boolean isProtectedBroadcast;
-        try {
-            isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Remote exception", e);
-            scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                    userId, brOptions, callingUid, callerPackage);
-            return ActivityManager.BROADCAST_SUCCESS;
-        }
-
-        final boolean isCallerSystem;
-        switch (UserHandle.getAppId(callingUid)) {
-            case ROOT_UID:
-            case SYSTEM_UID:
-            case PHONE_UID:
-            case BLUETOOTH_UID:
-            case NFC_UID:
-            case SE_UID:
-            case NETWORK_STACK_UID:
-                isCallerSystem = true;
-                break;
-            default:
-                isCallerSystem = (callerApp != null) && callerApp.isPersistent();
-                break;
-        }
-
-        // First line security check before anything else: stop non-system apps from
-        // sending protected broadcasts.
-        if (!isCallerSystem) {
-            if (isProtectedBroadcast) {
-                String msg = "Permission Denial: not allowed to send broadcast "
-                        + action + " from pid="
-                        + callingPid + ", uid=" + callingUid;
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-
-            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
-                    || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
-                // Special case for compatibility: we don't want apps to send this,
-                // but historically it has not been protected and apps may be using it
-                // to poke their own app widget.  So, instead of making it protected,
-                // just limit it to the caller.
-                if (callerPackage == null) {
-                    String msg = "Permission Denial: not allowed to send broadcast "
-                            + action + " from unknown caller.";
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                } else if (intent.getComponent() != null) {
-                    // They are good enough to send to an explicit component...  verify
-                    // it is being sent to the calling app.
-                    if (!intent.getComponent().getPackageName().equals(
-                            callerPackage)) {
-                        String msg = "Permission Denial: not allowed to send broadcast "
-                                + action + " to "
-                                + intent.getComponent().getPackageName() + " from "
-                                + callerPackage;
-                        Slog.w(TAG, msg);
-                        throw new SecurityException(msg);
-                    }
-                } else {
-                    // Limit broadcast to their own package.
-                    intent.setPackage(callerPackage);
-                }
-            }
-        }
-
-        boolean timeoutExempt = false;
-
-        if (action != null) {
-            if (getBackgroundLaunchBroadcasts().contains(action)) {
-                if (DEBUG_BACKGROUND_CHECK) {
-                    Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
-                }
-                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            }
-
-            // TODO: b/329211459 - Remove this after background remote intent is fixed.
-            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
-                    && getWearRemoteIntentAction().equals(action)) {
-                final int callerProcState = callerApp != null
-                        ? callerApp.getCurProcState()
-                        : ActivityManager.PROCESS_STATE_NONEXISTENT;
-                if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
-                        > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
-                    return ActivityManager.START_CANCELED;
-                }
-            }
-
-            switch (action) {
-                case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
-                    UserManagerInternal umInternal = LocalServices.getService(
-                            UserManagerInternal.class);
-                    UserInfo userInfo = umInternal.getUserInfo(userId);
-                    if (userInfo != null && userInfo.isCloneProfile()) {
-                        userId = umInternal.getProfileParentId(userId);
-                    }
-                    break;
-                case Intent.ACTION_UID_REMOVED:
-                case Intent.ACTION_PACKAGE_REMOVED:
-                case Intent.ACTION_PACKAGE_CHANGED:
-                case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
-                case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
-                case Intent.ACTION_PACKAGES_SUSPENDED:
-                case Intent.ACTION_PACKAGES_UNSUSPENDED:
-                    // Handle special intents: if this broadcast is from the package
-                    // manager about a package being removed, we need to remove all of
-                    // its activities from the history stack.
-                    if (checkComponentPermission(
-                            android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
-                            callingPid, callingUid, -1, true)
-                            != PackageManager.PERMISSION_GRANTED) {
-                        String msg = "Permission Denial: " + intent.getAction()
-                                + " broadcast from " + callerPackage + " (pid=" + callingPid
-                                + ", uid=" + callingUid + ")"
-                                + " requires "
-                                + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
-                        Slog.w(TAG, msg);
-                        throw new SecurityException(msg);
-                    }
-                    switch (action) {
-                        case Intent.ACTION_UID_REMOVED:
-                            final int uid = getUidFromIntent(intent);
-                            if (uid >= 0) {
-                                mBatteryStatsService.removeUid(uid);
-                                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                                    mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
-                                            intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
-                                } else {
-                                    mAppOpsService.uidRemoved(uid);
-                                    mServices.onUidRemovedLocked(uid);
-                                }
-                            }
-                            break;
-                        case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
-                            // If resources are unavailable just force stop all those packages
-                            // and flush the attribute cache as well.
-                            String list[] =
-                                    intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                            if (list != null && list.length > 0) {
-                                for (int i = 0; i < list.length; i++) {
-                                    forceStopPackageLocked(list[i], -1, false, true, true,
-                                            false, false, false, userId, "storage unmount");
-                                }
-                                mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
-                                sendPackageBroadcastLocked(
-                                        ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
-                                        list, userId);
-                            }
-                            break;
-                        case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
-                            mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
-                            break;
-                        case Intent.ACTION_PACKAGE_REMOVED:
-                        case Intent.ACTION_PACKAGE_CHANGED:
-                            Uri data = intent.getData();
-                            String ssp;
-                            if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
-                                boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
-                                final boolean replacing =
-                                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-                                final boolean killProcess =
-                                        !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
-                                final boolean fullUninstall = removed && !replacing;
-
-                                if (removed) {
-                                    if (killProcess) {
-                                        forceStopPackageLocked(ssp, UserHandle.getAppId(
-                                                intent.getIntExtra(Intent.EXTRA_UID, -1)),
-                                                false, true, true, false, fullUninstall, false,
-                                                userId, "pkg removed");
-                                        getPackageManagerInternal()
-                                                .onPackageProcessKilledForUninstall(ssp);
-                                    } else {
-                                        // Kill any app zygotes always, since they can't fork new
-                                        // processes with references to the old code
-                                        forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
-                                                intent.getIntExtra(Intent.EXTRA_UID, -1)),
-                                                userId);
-                                    }
-                                    final int cmd = killProcess
-                                            ? ApplicationThreadConstants.PACKAGE_REMOVED
-                                            : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
-                                    sendPackageBroadcastLocked(cmd,
-                                            new String[] {ssp}, userId);
-                                    if (fullUninstall) {
-                                        // Remove all permissions granted from/to this package
-                                        mUgmInternal.removeUriPermissionsForPackage(ssp, userId,
-                                                true, false);
-
-                                        mAtmInternal.removeRecentTasksByPackageName(ssp, userId);
-
-                                        mServices.forceStopPackageLocked(ssp, userId);
-                                        mAtmInternal.onPackageUninstalled(ssp, userId);
-                                        mBatteryStatsService.notePackageUninstalled(ssp);
-                                    }
-                                } else {
-                                    if (killProcess) {
-                                        int reason;
-                                        int subReason;
-                                        if (replacing) {
-                                            reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
-                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
-                                        } else {
-                                            reason =
-                                                    ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
-                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
-                                        }
-
-                                        final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
-                                                -1);
-                                        synchronized (mProcLock) {
-                                            mProcessList.killPackageProcessesLSP(ssp,
-                                                    UserHandle.getAppId(extraUid),
-                                                    userId, ProcessList.INVALID_ADJ,
-                                                    reason,
-                                                    subReason,
-                                                    "change " + ssp);
-                                        }
-                                    }
-                                    cleanupDisabledPackageComponentsLocked(ssp, userId,
-                                            intent.getStringArrayExtra(
-                                                    Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
-                                    mServices.schedulePendingServiceStartLocked(ssp, userId);
-                                }
-                            }
-                            break;
-                        case Intent.ACTION_PACKAGES_SUSPENDED:
-                        case Intent.ACTION_PACKAGES_UNSUSPENDED:
-                            final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
-                                    intent.getAction());
-                            final String[] packageNames = intent.getStringArrayExtra(
-                                    Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                            final int userIdExtra = intent.getIntExtra(
-                                    Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-
-                            mAtmInternal.onPackagesSuspendedChanged(packageNames, suspended,
-                                    userIdExtra);
-
-                            final boolean quarantined = intent.getBooleanExtra(
-                                    Intent.EXTRA_QUARANTINED, false);
-                            if (suspended && quarantined && packageNames != null) {
-                                for (int i = 0; i < packageNames.length; i++) {
-                                    forceStopPackage(packageNames[i], userId,
-                                            ActivityManager.FLAG_OR_STOPPED, "quarantined");
-                                }
-                            }
-
-                            break;
-                    }
-                    break;
-                case Intent.ACTION_PACKAGE_REPLACED:
-                {
-                    final Uri data = intent.getData();
-                    final String ssp;
-                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        ApplicationInfo aInfo = null;
-                        try {
-                            aInfo = AppGlobals.getPackageManager()
-                                    .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
-                        } catch (RemoteException ignore) {}
-                        if (aInfo == null) {
-                            Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
-                                    + " ssp=" + ssp + " data=" + data);
-                            scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                                    userId, brOptions, callingUid, callerPackage);
-                            return ActivityManager.BROADCAST_SUCCESS;
-                        }
-                        updateAssociationForApp(aInfo);
-                        mAtmInternal.onPackageReplaced(aInfo);
-                        mServices.updateServiceApplicationInfoLocked(aInfo);
-                        sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
-                                new String[] {ssp}, userId);
-                    }
-                    break;
-                }
-                case Intent.ACTION_PACKAGE_ADDED:
-                {
-                    // Special case for adding a package: by default turn on compatibility mode.
-                    Uri data = intent.getData();
-                    String ssp;
-                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        final boolean replacing =
-                                intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-                        mAtmInternal.onPackageAdded(ssp, replacing);
-
-                        try {
-                            ApplicationInfo ai = AppGlobals.getPackageManager().
-                                    getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
-                            mBatteryStatsService.notePackageInstalled(ssp,
-                                    ai != null ? ai.longVersionCode : 0);
-                        } catch (RemoteException e) {
-                        }
-                    }
-                    break;
-                }
-                case Intent.ACTION_PACKAGE_DATA_CLEARED:
-                {
-                    Uri data = intent.getData();
-                    String ssp;
-                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        mAtmInternal.onPackageDataCleared(ssp, userId);
-                    }
-                    break;
-                }
-                case Intent.ACTION_TIMEZONE_CHANGED:
-                    // If this is the time zone changed action, queue up a message that will reset
-                    // the timezone of all currently running processes. This message will get
-                    // queued up before the broadcast happens.
-                    mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
-                    break;
-                case Intent.ACTION_TIME_CHANGED:
-                    // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
-                    // the tri-state value it may contain and "unknown".
-                    // For convenience we re-use the Intent extra values.
-                    final int NO_EXTRA_VALUE_FOUND = -1;
-                    final int timeFormatPreferenceMsgValue = intent.getIntExtra(
-                            Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
-                            NO_EXTRA_VALUE_FOUND /* defaultValue */);
-                    // Only send a message if the time preference is available.
-                    if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
-                        Message updateTimePreferenceMsg =
-                                mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
-                                        timeFormatPreferenceMsgValue, 0);
-                        mHandler.sendMessage(updateTimePreferenceMsg);
-                    }
-                    mBatteryStatsService.noteCurrentTimeChanged();
-                    break;
-                case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
-                    mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
-                    break;
-                case Proxy.PROXY_CHANGE_ACTION:
-                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
-                    break;
-                case android.hardware.Camera.ACTION_NEW_PICTURE:
-                case android.hardware.Camera.ACTION_NEW_VIDEO:
-                    // In N we just turned these off; in O we are turing them back on partly,
-                    // only for registered receivers.  This will still address the main problem
-                    // (a spam of apps waking up when a picture is taken putting significant
-                    // memory pressure on the system at a bad point), while still allowing apps
-                    // that are already actively running to know about this happening.
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    break;
-                case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
-                    mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
-                    break;
-                case "com.android.launcher.action.INSTALL_SHORTCUT":
-                    // As of O, we no longer support this broadcasts, even for pre-O apps.
-                    // Apps should now be using ShortcutManager.pinRequestShortcut().
-                    Log.w(TAG, "Broadcast " + action
-                            + " no longer supported. It will not be delivered.");
-                    scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                            userId, brOptions, callingUid, callerPackage);
-                    return ActivityManager.BROADCAST_SUCCESS;
-                case Intent.ACTION_PRE_BOOT_COMPLETED:
-                    timeoutExempt = true;
-                    break;
-                case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
-                    if (!mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
-                            callerPackage)) {
-                        scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                                userId, brOptions, callingUid, callerPackage);
-                        // Returning success seems to be the pattern here
-                        return ActivityManager.BROADCAST_SUCCESS;
-                    }
-                    break;
-            }
-
-            if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
-                    Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
-                    Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
-                final int uid = getUidFromIntent(intent);
-                if (uid != -1) {
-                    final UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
-                    if (uidRec != null) {
-                        uidRec.updateHasInternetPermission();
-                    }
-                }
-            }
-        }
-
-        // Add to the sticky list if requested.
-        if (sticky) {
-            if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
-                    callingPid, callingUid)
-                    != PackageManager.PERMISSION_GRANTED) {
-                String msg =
-                        "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
-                            + " pid="
-                                + callingPid
-                                + ", uid="
-                                + callingUid
-                                + " requires "
-                                + android.Manifest.permission.BROADCAST_STICKY;
-                Slog.w(TAG, msg);
-                throw new SecurityException(msg);
-            }
-            if (requiredPermissions != null && requiredPermissions.length > 0) {
-                Slog.w(TAG, "Can't broadcast sticky intent " + intent
-                        + " and enforce permissions " + Arrays.toString(requiredPermissions));
-                scheduleCanceledResultTo(resultToApp, resultTo, intent,
-                        userId, brOptions, callingUid, callerPackage);
-                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
-            }
-            if (intent.getComponent() != null) {
-                throw new SecurityException(
-                        "Sticky broadcasts can't target a specific component");
-            }
-            synchronized (mStickyBroadcasts) {
-                // We use userId directly here, since the "all" target is maintained
-                // as a separate set of sticky broadcasts.
-                if (userId != UserHandle.USER_ALL) {
-                    // But first, if this is not a broadcast to all users, then
-                    // make sure it doesn't conflict with an existing broadcast to
-                    // all users.
-                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
-                            UserHandle.USER_ALL);
-                    if (stickies != null) {
-                        ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
-                        if (list != null) {
-                            int N = list.size();
-                            int i;
-                            for (i = 0; i < N; i++) {
-                                if (intent.filterEquals(list.get(i).intent)) {
-                                    throw new IllegalArgumentException("Sticky broadcast " + intent
-                                            + " for user " + userId
-                                            + " conflicts with existing global broadcast");
-                                }
-                            }
-                        }
-                    }
-                }
-                ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
-                        mStickyBroadcasts.get(userId);
-                if (stickies == null) {
-                    stickies = new ArrayMap<>();
-                    mStickyBroadcasts.put(userId, stickies);
-                }
-                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
-                if (list == null) {
-                    list = new ArrayList<>();
-                    stickies.put(intent.getAction(), list);
-                }
-                final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
-                        callingUid, brOptions, resultTo, ordered,
-                        BroadcastRecord.calculateUrgent(intent, brOptions));
-                final int stickiesCount = list.size();
-                int i;
-                for (i = 0; i < stickiesCount; i++) {
-                    if (intent.filterEquals(list.get(i).intent)) {
-                        // This sticky already exists, replace it.
-                        list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
-                                callingUid, callerAppProcessState, resolvedType));
-                        break;
-                    }
-                }
-                if (i >= stickiesCount) {
-                    list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
-                            callingUid, callerAppProcessState, resolvedType));
-                }
-            }
-        }
-
-        int[] users;
-        if (userId == UserHandle.USER_ALL) {
-            // Caller wants broadcast to go to all started users.
-            users = mUserController.getStartedUserArray();
-        } else {
-            // Caller wants broadcast to go to one specific user.
-            users = new int[] {userId};
-        }
-
-        var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
-                true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
-        args.platformCompat = mPlatformCompat;
-
-        // Figure out who all will receive this broadcast.
-        final int cookie = BroadcastQueue.traceBegin("queryReceivers");
-        List receivers = null;
-        List<BroadcastFilter> registeredReceivers = null;
-        // Need to resolve the intent to interested receivers...
-        if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-            receivers = collectReceiverComponents(
-                    intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
-        }
-        if (intent.getComponent() == null) {
-            final PackageDataSnapshot snapshot = getPackageManagerInternal().snapshot();
-            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
-                // Query one target user at a time, excluding shell-restricted users
-                for (int i = 0; i < users.length; i++) {
-                    if (mUserController.hasUserRestriction(
-                            UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
-                        continue;
-                    }
-                    List<BroadcastFilter> registeredReceiversForUser =
-                            mReceiverResolver.queryIntent(snapshot, intent,
-                                    resolvedType, false /*defaultOnly*/, users[i]);
-                    if (registeredReceivers == null) {
-                        registeredReceivers = registeredReceiversForUser;
-                    } else if (registeredReceiversForUser != null) {
-                        registeredReceivers.addAll(registeredReceiversForUser);
-                    }
-                }
-            } else {
-                registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
-                        resolvedType, false /*defaultOnly*/, userId);
-            }
-            if (registeredReceivers != null) {
-                SaferIntentUtils.blockNullAction(args, registeredReceivers);
-            }
-        }
-        BroadcastQueue.traceEnd(cookie);
-
-        final boolean replacePending =
-                (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
-                + " replacePending=" + replacePending);
-        if (registeredReceivers != null && broadcastAllowList != null) {
-            // if a uid whitelist was provided, remove anything in the application space that wasn't
-            // in it.
-            for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
-                final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
-                if (owningAppId >= Process.FIRST_APPLICATION_UID
-                        && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
-                    registeredReceivers.remove(i);
-                }
-            }
-        }
-
-        int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
-
-        // Merge into one list.
-        int ir = 0;
-        if (receivers != null) {
-            // A special case for PACKAGE_ADDED: do not allow the package
-            // being added to see this broadcast.  This prevents them from
-            // using this as a back door to get run as soon as they are
-            // installed.  Maybe in the future we want to have a special install
-            // broadcast or such for apps, but we'd like to deliberately make
-            // this decision.
-            String skipPackages[] = null;
-            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
-                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
-                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
-                Uri data = intent.getData();
-                if (data != null) {
-                    String pkgName = data.getSchemeSpecificPart();
-                    if (pkgName != null) {
-                        skipPackages = new String[] { pkgName };
-                    }
-                }
-            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
-                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            }
-            if (skipPackages != null && (skipPackages.length > 0)) {
-                for (String skipPackage : skipPackages) {
-                    if (skipPackage != null) {
-                        int NT = receivers.size();
-                        for (int it=0; it<NT; it++) {
-                            ResolveInfo curt = (ResolveInfo)receivers.get(it);
-                            if (curt.activityInfo.packageName.equals(skipPackage)) {
-                                receivers.remove(it);
-                                it--;
-                                NT--;
-                            }
-                        }
-                    }
-                }
-            }
-
-            int NT = receivers != null ? receivers.size() : 0;
-            int it = 0;
-            ResolveInfo curt = null;
-            BroadcastFilter curr = null;
-            while (it < NT && ir < NR) {
-                if (curt == null) {
-                    curt = (ResolveInfo)receivers.get(it);
-                }
-                if (curr == null) {
-                    curr = registeredReceivers.get(ir);
-                }
-                if (curr.getPriority() >= curt.priority) {
-                    // Insert this broadcast record into the final list.
-                    receivers.add(it, curr);
-                    ir++;
-                    curr = null;
-                    it++;
-                    NT++;
-                } else {
-                    // Skip to the next ResolveInfo in the final list.
-                    it++;
-                    curt = null;
-                }
-            }
-        }
-        while (ir < NR) {
-            if (receivers == null) {
-                receivers = new ArrayList();
-            }
-            receivers.add(registeredReceivers.get(ir));
-            ir++;
-        }
-
-        if (isCallerSystem) {
-            checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
-                    isProtectedBroadcast, receivers);
-        }
-
-        if ((receivers != null && receivers.size() > 0)
-                || resultTo != null) {
-            BroadcastQueue queue = mBroadcastQueue;
-            SaferIntentUtils.filterNonExportedComponents(args, receivers);
-            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
-                    callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
-                    requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
-                    receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
-                    ordered, sticky, false, userId,
-                    backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
-                    callerAppProcessState);
-            broadcastSentEventRecord.setBroadcastRecord(r);
-
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
-            queue.enqueueBroadcastLocked(r);
-        } else {
-            // There was nobody interested in the broadcast, but we still want to record
-            // that it happened.
-            if (intent.getComponent() == null && intent.getPackage() == null
-                    && (intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                // This was an implicit broadcast... let's record it for posterity.
-                addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
-            }
-        }
-
-        return ActivityManager.BROADCAST_SUCCESS;
-    }
-
-    @GuardedBy("this")
-    private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
-            Intent intent, int userId, BroadcastOptions options, int callingUid,
-            String callingPackage) {
-        if (resultTo == null) {
-            return;
-        }
-        final ProcessRecord app = resultToApp;
-        final IApplicationThread thread  = (app != null) ? app.getOnewayThread() : null;
-        if (thread != null) {
-            try {
-                final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
-                thread.scheduleRegisteredReceiver(
-                        resultTo, intent, Activity.RESULT_CANCELED, null, null,
-                        false, false, true, userId, app.mState.getReportedProcState(),
-                        shareIdentity ? callingUid : Process.INVALID_UID,
-                        shareIdentity ? callingPackage : null);
-            } catch (RemoteException e) {
-                final String msg = "Failed to schedule result of " + intent + " via "
-                        + app + ": " + e;
-                app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
-                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
-                Slog.d(TAG, msg);
-            }
-        }
-    }
-
-    @GuardedBy("this")
-    private int getRealProcessStateLocked(ProcessRecord app, int pid) {
-        if (app == null) {
-            synchronized (mPidsSelfLocked) {
-                app = mPidsSelfLocked.get(pid);
-            }
-        }
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            return app.mState.getCurProcState();
-        }
-        return PROCESS_STATE_NONEXISTENT;
-    }
-
-    @GuardedBy("this")
-    private int getRealUidStateLocked(ProcessRecord app, int pid) {
-        if (app == null) {
-            synchronized (mPidsSelfLocked) {
-                app = mPidsSelfLocked.get(pid);
-            }
-        }
-        if (app != null && app.getThread() != null && !app.isKilled()) {
-            final UidRecord uidRecord = app.getUidRecord();
-            if (uidRecord != null) {
-                return uidRecord.getCurProcState();
-            }
-        }
-        return PROCESS_STATE_NONEXISTENT;
-    }
-
-    @VisibleForTesting
-    ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
-        synchronized (mStickyBroadcasts) {
-            final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
-                    mStickyBroadcasts.get(userId);
-            if (stickyBroadcasts == null) {
-                return null;
-            }
-            return stickyBroadcasts.get(action);
-        }
-    }
-
-    /**
-     * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
-     */
-    private int getUidFromIntent(Intent intent) {
-        if (intent == null) {
-            return -1;
-        }
-        final Bundle intentExtras = intent.getExtras();
-        return intent.hasExtra(Intent.EXTRA_UID)
-                ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
-    }
-
-    final void rotateBroadcastStatsIfNeededLocked() {
-        final long now = SystemClock.elapsedRealtime();
-        if (mCurBroadcastStats == null ||
-                (mCurBroadcastStats.mStartRealtime +(24*60*60*1000) < now)) {
-            mLastBroadcastStats = mCurBroadcastStats;
-            if (mLastBroadcastStats != null) {
-                mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
-                mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
-            }
-            mCurBroadcastStats = new BroadcastStats();
-        }
-    }
-
-    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
-            int skipCount, long dispatchTime) {
-        rotateBroadcastStatsIfNeededLocked();
-        mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
-    }
-
-    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
-        rotateBroadcastStatsIfNeededLocked();
-        mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
-    }
-
-    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
-        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
-        final String callerPackage = info != null ? info.packageName : original.callerPackage;
-        if (callerPackage != null) {
-            mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
-                    original.callingUid, 0, callerPackage).sendToTarget();
-        }
-    }
-
-    final Intent verifyBroadcastLocked(Intent intent) {
-        if (intent != null) {
-            intent.prepareToEnterSystemServer();
-        }
-
-        int flags = intent.getFlags();
-
-        if (!mProcessesReady) {
-            // if the caller really truly claims to know what they're doing, go
-            // ahead and allow the broadcast without launching any receivers
-            if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
-                // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
-            } else if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
-                Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
-                        + " before boot completion");
-                throw new IllegalStateException("Cannot broadcast before boot completed");
-            }
-        }
-
-        if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
-            throw new IllegalArgumentException(
-                    "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
-        }
-
-        if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
-            switch (Binder.getCallingUid()) {
-                case ROOT_UID:
-                case SHELL_UID:
-                    break;
-                default:
-                    Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
-                            + Binder.getCallingUid());
-                    intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
-                    break;
-            }
-        }
-
-        return intent;
-    }
-
     /**
      * @deprecated Use {@link #broadcastIntentWithFeature}
      */
@@ -16338,110 +14276,14 @@
             String[] requiredPermissions, String[] excludedPermissions,
             String[] excludedPackages, int appOp, Bundle bOptions,
             boolean serialized, boolean sticky, int userId) {
-        enforceNotIsolatedCaller("broadcastIntent");
-
-        synchronized(this) {
-            intent = verifyBroadcastLocked(intent);
-
-            final ProcessRecord callerApp = getRecordForAppLOSP(caller);
-            final int callingPid = Binder.getCallingPid();
-            final int callingUid = Binder.getCallingUid();
-
-            // We're delivering the result to the caller
-            final ProcessRecord resultToApp = callerApp;
-
-            // Permission regimes around sender-supplied broadcast options.
-            enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
-
-            final ComponentName cn = intent.getComponent();
-
-            Trace.traceBegin(
-                    Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                    "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
-
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                return broadcastIntentLocked(callerApp,
-                        callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
-                        intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
-                        resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
-                        appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
-                        callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-            }
-        }
-    }
-
-    // Not the binder call surface
-    int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
-            int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
-            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
-            String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
-            boolean serialized, boolean sticky, int userId,
-            BackgroundStartPrivileges backgroundStartPrivileges,
-            @Nullable int[] broadcastAllowList) {
-        synchronized(this) {
-            intent = verifyBroadcastLocked(intent);
-
-            final long origId = Binder.clearCallingIdentity();
-            String[] requiredPermissions = requiredPermission == null ? null
-                    : new String[] {requiredPermission};
-            try {
-                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
-                        resultToApp, resultTo, resultCode, resultData, resultExtras,
-                        requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
-                        uid, realCallingUid, realCallingPid, userId,
-                        backgroundStartPrivileges, broadcastAllowList,
-                        null /* filterExtrasForReceiver */);
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
+        return mBroadcastController.broadcastIntentWithFeature(caller, callingFeatureId, intent,
+                resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
+                excludedPermissions, excludedPackages, appOp, bOptions, serialized, sticky, userId);
     }
 
     @Override
     public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
-        // Refuse possible leaked file descriptors
-        if (intent != null && intent.hasFileDescriptors() == true) {
-            throw new IllegalArgumentException("File descriptors passed in Intent");
-        }
-
-        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
-
-        if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: unbroadcastIntent() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.BROADCAST_STICKY;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        synchronized (mStickyBroadcasts) {
-            ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
-            if (stickies != null) {
-                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
-                if (list != null) {
-                    int N = list.size();
-                    int i;
-                    for (i = 0; i < N; i++) {
-                        if (intent.filterEquals(list.get(i).intent)) {
-                            list.remove(i);
-                            break;
-                        }
-                    }
-                    if (list.size() <= 0) {
-                        stickies.remove(intent.getAction());
-                    }
-                }
-                if (stickies.size() <= 0) {
-                    mStickyBroadcasts.remove(userId);
-                }
-            }
-        }
+        mBroadcastController.unbroadcastIntent(caller, intent, userId);
     }
 
     void backgroundServicesFinishedLocked(int userId) {
@@ -16450,31 +14292,32 @@
 
     public void finishReceiver(IBinder caller, int resultCode, String resultData,
             Bundle resultExtras, boolean resultAbort, int flags) {
-        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+        mBroadcastController.finishReceiver(caller, resultCode, resultData, resultExtras,
+                resultAbort, flags);
+    }
 
-        // Refuse possible leaked file descriptors
-        if (resultExtras != null && resultExtras.hasFileDescriptors()) {
-            throw new IllegalArgumentException("File descriptors passed in Bundle");
-        }
+    @VisibleForTesting
+    ArrayList<BroadcastController.StickyBroadcast> getStickyBroadcastsForTest(String action,
+            int userId) {
+        return mBroadcastController.getStickyBroadcastsForTest(action, userId);
+    }
 
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized(this) {
-                final ProcessRecord callerApp = getRecordForAppLOSP(caller);
-                if (callerApp == null) {
-                    Slog.w(TAG, "finishReceiver: no app for " + caller);
-                    return;
-                }
+    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+        mBroadcastController.notifyBroadcastFinishedLocked(original);
+    }
 
-                mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
-                        resultData, resultExtras, resultAbort, true);
-                // updateOomAdjLocked() will be done here
-                trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
-            }
+    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+            int skipCount, long dispatchTime) {
+        mBroadcastController.addBroadcastStatLocked(action, srcPackage, receiveCount, skipCount,
+                dispatchTime);
+    }
 
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
+    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+        mBroadcastController.addBackgroundCheckViolationLocked(action, targetPackage);
+    }
+
+    void removeReceiverLocked(ReceiverList rl) {
+        mBroadcastController.removeReceiverLocked(rl);
     }
 
     // =========================================================
@@ -17839,7 +15682,7 @@
     }
 
     @GuardedBy("this")
-    private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
+    void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
         // First remove any unused application processes whose package
         // has been removed.
         boolean didSomething = false;
@@ -18418,43 +16261,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 {
@@ -18829,7 +16684,7 @@
 
         @Override
         public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
-            enforceBroadcastOptionPermissionsInternal(options, callingUid);
+            mBroadcastController.enforceBroadcastOptionPermissionsInternal(options, callingUid);
         }
 
         /**
@@ -19219,7 +17074,7 @@
                 @Nullable int[] broadcastAllowList) {
             synchronized (ActivityManagerService.this) {
                 final ProcessRecord resultToApp = getRecordForAppLOSP(resultToThread);
-                return ActivityManagerService.this.broadcastIntentInPackage(packageName, featureId,
+                return mBroadcastController.broadcastIntentInPackage(packageName, featureId,
                         uid, realCallingUid, realCallingPid, intent, resolvedType, resultToApp,
                         resultTo, resultCode, resultData, resultExtras, requiredPermission,
                         bOptions, serialized, sticky, userId,
@@ -19236,13 +17091,13 @@
                 @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
                 @Nullable Bundle bOptions) {
             synchronized (ActivityManagerService.this) {
-                intent = verifyBroadcastLocked(intent);
+                intent = mBroadcastController.verifyBroadcastLocked(intent);
 
                 final int callingPid = Binder.getCallingPid();
                 final int callingUid = Binder.getCallingUid();
                 final long origId = Binder.clearCallingIdentity();
                 try {
-                    return ActivityManagerService.this.broadcastIntentLocked(null /*callerApp*/,
+                    return mBroadcastController.broadcastIntentLocked(null /*callerApp*/,
                             null /*callerPackage*/, null /*callingFeatureId*/, intent,
                             null /* resolvedType */, null /* resultToApp */, resultTo,
                             0 /* resultCode */, null /* resultData */,
@@ -21159,26 +19014,6 @@
         }
     }
 
-    /**
-     * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
-     * message on {@code pw} when it cannot be parsed.
-     *
-     * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
-     */
-    private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
-        if (index > args.length) {
-            pw.println("Missing argument");
-            return invalidValue;
-        }
-        String arg = args[index];
-        try {
-            return Integer.parseInt(arg);
-        } catch (Exception e) {
-            pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
-            return invalidValue;
-        }
-    }
-
     private void notifyMediaProjectionEvent(int uid, @NonNull IBinder projectionToken,
             @MediaProjectionTokenEvent int event) {
         synchronized (mMediaProjectionTokenMap) {
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/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 4a7ad31..1b00cec 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -16,7 +16,6 @@
 
 package com.android.server.am;
 
-import static android.app.ApplicationStartInfo.START_TIMESTAMP_LAUNCH;
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -51,6 +50,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ProcessMap;
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
 import com.android.server.IoThread;
 import com.android.server.ServiceThread;
 import com.android.server.SystemServiceManager;
@@ -107,6 +108,16 @@
 
     @VisibleForTesting boolean mEnabled = false;
 
+    /**
+     * Monotonic clock which does not reset on reboot.
+     *
+     * Time for offset is persisted along with records, see {@link #persistProcessStartInfo}.
+     * This does not follow the recommendation of {@link MonotonicClock} to persist on shutdown as
+     * it's ok in this case to lose any time change past the last persist as records added since
+     * then will be lost as well and the purpose of this clock is to keep records in order.
+     */
+    @VisibleForTesting MonotonicClock mMonotonicClock = null;
+
     /** Initialized in {@link #init} and read-only after that. */
     @VisibleForTesting ActivityManagerService mService;
 
@@ -203,6 +214,15 @@
         IoThread.getHandler().post(() -> {
             loadExistingProcessStartInfo();
         });
+
+        if (mMonotonicClock == null) {
+            // This should only happen if there are no persisted records, or if the records were
+            // persisted by a version without the monotonic clock. Either way, create a new clock
+            // with no offset. In the case of records with no monotonic time the value will default
+            // to 0 and all new records will correctly end up in front of them.
+            mMonotonicClock = new MonotonicClock(Clock.SYSTEM_CLOCK.elapsedRealtime(),
+                    Clock.SYSTEM_CLOCK);
+        }
     }
 
     /**
@@ -264,7 +284,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo();
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.setIntent(intent);
             start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
@@ -396,7 +416,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo();
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
             addBaseFieldsFromProcessRecord(start, app);
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
@@ -422,7 +442,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo();
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
             addBaseFieldsFromProcessRecord(start, app);
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
@@ -444,7 +464,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo();
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
             addBaseFieldsFromProcessRecord(start, app);
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
@@ -461,7 +481,7 @@
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo();
+            ApplicationStartInfo start = new ApplicationStartInfo(getMonotonicTime());
             addBaseFieldsFromProcessRecord(start, app);
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
             start.addStartupTimestamp(
@@ -632,7 +652,8 @@
 
                     Collections.sort(
                             list, (a, b) ->
-                            Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+                            Long.compare(b.getMonoticCreationTimeMs(),
+                                    a.getMonoticCreationTimeMs()));
                     int size = list.size();
                     if (maxNum > 0) {
                         size = Math.min(size, maxNum);
@@ -898,6 +919,10 @@
                     case (int) AppsStartInfoProto.PACKAGES:
                         loadPackagesFromProto(proto, next);
                         break;
+                    case (int) AppsStartInfoProto.MONOTONIC_TIME:
+                        long monotonicTime = proto.readLong(AppsStartInfoProto.MONOTONIC_TIME);
+                        mMonotonicClock = new MonotonicClock(monotonicTime, Clock.SYSTEM_CLOCK);
+                        break;
                 }
             }
         } catch (IOException | IllegalArgumentException | WireTypeMismatchException
@@ -979,6 +1004,7 @@
                     mLastAppStartInfoPersistTimestamp = now;
                 }
             }
+            proto.write(AppsStartInfoProto.MONOTONIC_TIME, getMonotonicTime());
             if (succeeded) {
                 proto.flush();
                 af.finishWrite(out);
@@ -1099,13 +1125,12 @@
         }
     }
 
-    /** Convenience method to obtain timestamp of beginning of start.*/
-    private static long getStartTimestamp(ApplicationStartInfo startInfo) {
-        if (startInfo.getStartupTimestamps() == null
-                    || !startInfo.getStartupTimestamps().containsKey(START_TIMESTAMP_LAUNCH)) {
-            return -1;
+    private long getMonotonicTime() {
+        if (mMonotonicClock == null) {
+            // This should never happen. Return 0 to not interfere with past or future records.
+            return 0;
         }
-        return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH);
+        return mMonotonicClock.monotonicTime();
     }
 
     /** A container class of (@link android.app.ApplicationStartInfo) */
@@ -1143,7 +1168,7 @@
 
             // Sort records so we can remove the least recent ones.
             Collections.sort(mInfos, (a, b) ->
-                    Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+                    Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
 
             // Remove records and trim list object back to size.
             mInfos.subList(0, mInfos.size() - getMaxCapacity()).clear();
@@ -1165,8 +1190,8 @@
                 long oldestTimeStamp = Long.MAX_VALUE;
                 for (int i = 0; i < size; i++) {
                     ApplicationStartInfo startInfo = mInfos.get(i);
-                    if (getStartTimestamp(startInfo) < oldestTimeStamp) {
-                        oldestTimeStamp = getStartTimestamp(startInfo);
+                    if (startInfo.getMonoticCreationTimeMs() < oldestTimeStamp) {
+                        oldestTimeStamp = startInfo.getMonoticCreationTimeMs();
                         oldestIndex = i;
                     }
                 }
@@ -1176,7 +1201,7 @@
             }
             mInfos.add(info);
             Collections.sort(mInfos, (a, b) ->
-                    Long.compare(getStartTimestamp(b), getStartTimestamp(a)));
+                    Long.compare(b.getMonoticCreationTimeMs(), a.getMonoticCreationTimeMs()));
         }
 
         /**
@@ -1337,7 +1362,9 @@
                         mUid = proto.readInt(AppsStartInfoProto.Package.User.UID);
                         break;
                     case (int) AppsStartInfoProto.Package.User.APP_START_INFO:
-                        ApplicationStartInfo info = new ApplicationStartInfo();
+                        // Create record with monotonic time 0 in case the persisted record does not
+                        // have a create time.
+                        ApplicationStartInfo info = new ApplicationStartInfo(0);
                         info.readFromProto(proto, AppsStartInfoProto.Package.User.APP_START_INFO);
                         mInfos.add(info);
                         break;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 5137b4c..955b75d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -123,31 +123,16 @@
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.power.optimization.Flags;
-import com.android.server.power.stats.AggregatedPowerStatsConfig;
-import com.android.server.power.stats.AmbientDisplayPowerStatsProcessor;
-import com.android.server.power.stats.AudioPowerStatsProcessor;
 import com.android.server.power.stats.BatteryExternalStatsWorker;
 import com.android.server.power.stats.BatteryStatsDumpHelperImpl;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.power.stats.BatteryUsageStatsProvider;
-import com.android.server.power.stats.BluetoothPowerStatsProcessor;
-import com.android.server.power.stats.CameraPowerStatsProcessor;
-import com.android.server.power.stats.CpuPowerStatsProcessor;
-import com.android.server.power.stats.CustomEnergyConsumerPowerStatsProcessor;
-import com.android.server.power.stats.FlashlightPowerStatsProcessor;
-import com.android.server.power.stats.GnssPowerStatsProcessor;
-import com.android.server.power.stats.MobileRadioPowerStatsProcessor;
-import com.android.server.power.stats.PhoneCallPowerStatsProcessor;
-import com.android.server.power.stats.PowerStatsAggregator;
-import com.android.server.power.stats.PowerStatsExporter;
+import com.android.server.power.stats.PowerAttributor;
 import com.android.server.power.stats.PowerStatsScheduler;
 import com.android.server.power.stats.PowerStatsStore;
 import com.android.server.power.stats.PowerStatsUidResolver;
-import com.android.server.power.stats.ScreenPowerStatsProcessor;
-import com.android.server.power.stats.SensorPowerStatsProcessor;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
-import com.android.server.power.stats.VideoPowerStatsProcessor;
-import com.android.server.power.stats.WifiPowerStatsProcessor;
+import com.android.server.power.stats.processor.MultiStatePowerAttributor;
 import com.android.server.power.stats.wakeups.CpuWakeupStats;
 
 import java.io.File;
@@ -207,7 +192,7 @@
     private final AtomicFile mConfigFile;
     private final BatteryStats.BatteryStatsDumpHelper mDumpHelper;
     private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver();
-    private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+    private final PowerAttributor mPowerAttributor;
 
     private volatile boolean mMonitorEnabled = true;
 
@@ -445,14 +430,12 @@
             mStats.startTrackingSystemServerCpuTime();
         }
 
-        mAggregatedPowerStatsConfig = createAggregatedPowerStatsConfig();
-        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig);
+        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler);
+        mPowerAttributor = new MultiStatePowerAttributor(mContext, mPowerStatsStore, mPowerProfile,
+                mCpuScalingPolicies, mPowerStatsUidResolver);
         mPowerStatsScheduler = createPowerStatsScheduler(mContext);
-        PowerStatsExporter powerStatsExporter =
-                new PowerStatsExporter(mPowerStatsStore,
-                        new PowerStatsAggregator(mAggregatedPowerStatsConfig, mStats.getHistory()));
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context,
-                powerStatsExporter, mPowerProfile, mCpuScalingPolicies,
+                mPowerAttributor, mPowerProfile, mCpuScalingPolicies,
                 mPowerStatsStore, Clock.SYSTEM_CLOCK);
         mStats.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, mPowerStatsStore);
         mDumpHelper = new BatteryStatsDumpHelperImpl(mBatteryUsageStatsProvider);
@@ -472,154 +455,11 @@
                             onAlarmListener, aHandler);
                 };
         return new PowerStatsScheduler(mStats::schedulePowerStatsSampleCollection,
-                new PowerStatsAggregator(mAggregatedPowerStatsConfig,
-                        mStats.getHistory()), aggregatedPowerStatsSpanDuration,
+                mStats.getHistory(), mPowerAttributor, aggregatedPowerStatsSpanDuration,
                 powerStatsAggregationPeriod, mPowerStatsStore, alarmScheduler, Clock.SYSTEM_CLOCK,
                 mMonotonicClock, () -> mStats.getHistory().getStartTime(), mHandler);
     }
 
-    private AggregatedPowerStatsConfig createAggregatedPowerStatsConfig() {
-        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(
-                        () -> new CpuPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SCREEN)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .setProcessorSupplier(
-                        () -> new ScreenPowerStatsProcessor(mPowerProfile));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
-                        BatteryConsumer.POWER_COMPONENT_SCREEN)
-                .setProcessorSupplier(AmbientDisplayPowerStatsProcessor::new);
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(
-                        () -> new MobileRadioPowerStatsProcessor(mPowerProfile));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_PHONE,
-                        BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
-                .setProcessorSupplier(PhoneCallPowerStatsProcessor::new);
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_WIFI)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(
-                        () -> new WifiPowerStatsProcessor(mPowerProfile));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(
-                        () -> new BluetoothPowerStatsProcessor(mPowerProfile));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AUDIO)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(
-                        () -> new AudioPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_VIDEO)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(
-                        () -> new VideoPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(
-                        () -> new FlashlightPowerStatsProcessor(mPowerProfile,
-                                mPowerStatsUidResolver));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CAMERA)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(
-                        () -> new CameraPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_GNSS)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(
-                        () -> new GnssPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
-
-        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SENSORS)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessorSupplier(() -> new SensorPowerStatsProcessor(
-                        () -> mContext.getSystemService(SensorManager.class)));
-
-        config.trackCustomPowerComponents(CustomEnergyConsumerPowerStatsProcessor::new)
-                .trackDeviceStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN)
-                .trackUidStates(
-                        AggregatedPowerStatsConfig.STATE_POWER,
-                        AggregatedPowerStatsConfig.STATE_SCREEN,
-                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
-        return config;
-    }
-
     private void setPowerStatsThrottlePeriods(BatteryStatsImpl.BatteryStatsConfig.Builder builder,
             String configString) {
         if (configString == null) {
@@ -664,79 +504,86 @@
     }
 
     public void systemServicesReady() {
+        MultiStatePowerAttributor attributor = (MultiStatePowerAttributor) mPowerAttributor;
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CPU,
                 Flags.streamlinedBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_CPU,
                 Flags.streamlinedBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_SCREEN,
                 Flags.streamlinedMiscBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_SCREEN,
                 Flags.streamlinedMiscBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
                 Flags.streamlinedMiscBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
                 Flags.streamlinedMiscBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                 Flags.streamlinedConnectivityBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                 Flags.streamlinedConnectivityBatteryStats());
+        attributor.setPowerComponentSupported(
+                BatteryConsumer.POWER_COMPONENT_PHONE,
+                Flags.streamlinedConnectivityBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_WIFI,
                 Flags.streamlinedConnectivityBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_WIFI,
                 Flags.streamlinedConnectivityBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
                 Flags.streamlinedConnectivityBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
                 Flags.streamlinedConnectivityBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_AUDIO,
                 Flags.streamlinedMiscBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_AUDIO,
                 Flags.streamlinedMiscBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_VIDEO,
                 Flags.streamlinedMiscBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_VIDEO,
                 Flags.streamlinedMiscBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
                 Flags.streamlinedMiscBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
                 Flags.streamlinedMiscBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_GNSS,
                 Flags.streamlinedMiscBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_GNSS,
                 Flags.streamlinedMiscBatteryStats());
 
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_SENSORS,
                 Flags.streamlinedMiscBatteryStats());
 
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CAMERA,
                 Flags.streamlinedMiscBatteryStats());
-        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+        attributor.setPowerComponentSupported(
                 BatteryConsumer.POWER_COMPONENT_CAMERA,
                 Flags.streamlinedMiscBatteryStats());
 
         // By convention POWER_COMPONENT_ANY represents custom Energy Consumers
         mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_ANY,
                 Flags.streamlinedMiscBatteryStats());
+        attributor.setPowerComponentSupported(
+                BatteryConsumer.POWER_COMPONENT_ANY,
+                Flags.streamlinedMiscBatteryStats());
 
         mWorker.systemServicesReady();
         mStats.systemServicesReady(mContext);
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
new file mode 100644
index 0000000..f7085b4
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -0,0 +1,2415 @@
+/*
+ * 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.am;
+
+import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
+import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.os.Process.BLUETOOTH_UID;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.NETWORK_STACK_UID;
+import static android.os.Process.NFC_UID;
+import static android.os.Process.PHONE_UID;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SE_UID;
+import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerService.CLEAR_DNS_CACHE_MSG;
+import static com.android.server.am.ActivityManagerService.HANDLE_TRUST_STORAGE_UPDATE_MSG;
+import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
+import static com.android.server.am.ActivityManagerService.TAG;
+import static com.android.server.am.ActivityManagerService.UPDATE_HTTP_PROXY_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_PREFERENCE_MSG;
+import static com.android.server.am.ActivityManagerService.UPDATE_TIME_ZONE;
+import static com.android.server.am.ActivityManagerService.checkComponentPermission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.ApplicationExitInfo;
+import android.app.ApplicationThreadConstants;
+import android.app.BackgroundStartPrivileges;
+import android.app.BroadcastOptions;
+import android.app.IApplicationThread;
+import android.app.compat.CompatChanges;
+import android.appwidget.AppWidgetManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.media.audiofx.AudioEffect;
+import android.net.ConnectivityManager;
+import android.net.Proxy;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.BinderProxy;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IntentResolver;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+import com.android.server.pm.Computer;
+import com.android.server.pm.SaferIntentUtils;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.snapshot.PackageDataSnapshot;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.utils.Slogf;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+class BroadcastController {
+    private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
+
+    /**
+     * It is now required for apps to explicitly set either
+     * {@link android.content.Context#RECEIVER_EXPORTED} or
+     * {@link android.content.Context#RECEIVER_NOT_EXPORTED} when registering a receiver for an
+     * unprotected broadcast in code.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
+
+    // Maximum number of receivers an app can register.
+    private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
+
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final ActivityManagerService mService;
+    @NonNull
+    private BroadcastQueue mBroadcastQueue;
+
+    @GuardedBy("mService")
+    BroadcastStats mLastBroadcastStats;
+
+    @GuardedBy("mService")
+    BroadcastStats mCurBroadcastStats;
+
+    /**
+     * Broadcast actions that will always be deliverable to unlaunched/background apps
+     */
+    @GuardedBy("mService")
+    private ArraySet<String> mBackgroundLaunchBroadcasts;
+
+    /**
+     * State of all active sticky broadcasts per user.  Keys are the action of the
+     * sticky Intent, values are an ArrayList of all broadcasted intents with
+     * that action (which should usually be one).  The SparseArray is keyed
+     * by the user ID the sticky is for, and can include UserHandle.USER_ALL
+     * for stickies that are sent to all users.
+     */
+    @GuardedBy("mStickyBroadcasts")
+    final SparseArray<ArrayMap<String, ArrayList<StickyBroadcast>>> mStickyBroadcasts =
+            new SparseArray<>();
+
+    /**
+     * Keeps track of all IIntentReceivers that have been registered for broadcasts.
+     * Hash keys are the receiver IBinder, hash value is a ReceiverList.
+     */
+    @GuardedBy("mService")
+    final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
+
+    /**
+     * Resolver for broadcast intents to registered receivers.
+     * Holds BroadcastFilter (subclass of IntentFilter).
+     */
+    final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver =
+            new IntentResolver<>() {
+        @Override
+        protected boolean allowFilterResult(
+                BroadcastFilter filter, List<BroadcastFilter> dest) {
+            IBinder target = filter.receiverList.receiver.asBinder();
+            for (int i = dest.size() - 1; i >= 0; i--) {
+                if (dest.get(i).receiverList.receiver.asBinder() == target) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected BroadcastFilter newResult(@NonNull Computer computer, BroadcastFilter filter,
+                int match, int userId, long customFlags) {
+            if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
+                    || userId == filter.owningUserId) {
+                return super.newResult(computer, filter, match, userId, customFlags);
+            }
+            return null;
+        }
+
+        @Override
+        protected IntentFilter getIntentFilter(@NonNull BroadcastFilter input) {
+            return input;
+        }
+
+        @Override
+        protected BroadcastFilter[] newArray(int size) {
+            return new BroadcastFilter[size];
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
+            return packageName.equals(filter.packageName);
+        }
+    };
+
+    BroadcastController(Context context, ActivityManagerService service, BroadcastQueue queue) {
+        mContext = context;
+        mService = service;
+        mBroadcastQueue = queue;
+    }
+
+    void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+        mBroadcastQueue = broadcastQueue;
+    }
+
+    Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
+            String callerFeatureId, String receiverId, IIntentReceiver receiver,
+            IntentFilter filter, String permission, int userId, int flags) {
+        traceRegistrationBegin(receiverId, receiver, filter, userId);
+        try {
+            return registerReceiverWithFeatureTraced(caller, callerPackage, callerFeatureId,
+                    receiverId, receiver, filter, permission, userId, flags);
+        } finally {
+            traceRegistrationEnd();
+        }
+    }
+
+    private static void traceRegistrationBegin(String receiverId, IIntentReceiver receiver,
+            IntentFilter filter, int userId) {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            final StringBuilder sb = new StringBuilder("registerReceiver: ");
+            sb.append(Binder.getCallingUid()); sb.append('/');
+            sb.append(receiverId == null ? "null" : receiverId); sb.append('/');
+            final int actionsCount = filter.safeCountActions();
+            if (actionsCount > 0) {
+                for (int i = 0; i < actionsCount; ++i) {
+                    sb.append(filter.getAction(i));
+                    if (i != actionsCount - 1) sb.append(',');
+                }
+            } else {
+                sb.append("null");
+            }
+            sb.append('/');
+            sb.append('u'); sb.append(userId); sb.append('/');
+            sb.append(receiver == null ? "null" : receiver.asBinder());
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, sb.toString());
+        }
+    }
+
+    private static void traceRegistrationEnd() {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    private Intent registerReceiverWithFeatureTraced(IApplicationThread caller,
+            String callerPackage, String callerFeatureId, String receiverId,
+            IIntentReceiver receiver, IntentFilter filter, String permission,
+            int userId, int flags) {
+        mService.enforceNotIsolatedCaller("registerReceiver");
+        ArrayList<StickyBroadcast> stickyBroadcasts = null;
+        ProcessRecord callerApp = null;
+        final boolean visibleToInstantApps =
+                (flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0;
+
+        int callingUid;
+        int callingPid;
+        boolean instantApp;
+        synchronized (mService.mProcLock) {
+            callerApp = mService.getRecordForAppLOSP(caller);
+            if (callerApp == null) {
+                Slog.w(TAG, "registerReceiverWithFeature: no app for " + caller);
+                return null;
+            }
+            if (callerApp.info.uid != SYSTEM_UID
+                    && !callerApp.getPkgList().containsKey(callerPackage)
+                    && !"android".equals(callerPackage)) {
+                throw new SecurityException("Given caller package " + callerPackage
+                        + " is not running in process " + callerApp);
+            }
+            callingUid = callerApp.info.uid;
+            callingPid = callerApp.getPid();
+
+            instantApp = isInstantApp(callerApp, callerPackage, callingUid);
+        }
+        userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+                ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
+
+        // Warn if system internals are registering for important broadcasts
+        // without also using a priority to ensure they process the event
+        // before normal apps hear about it
+        if (UserHandle.isCore(callingUid)) {
+            final int priority = filter.getPriority();
+            final boolean systemPriority = (priority >= IntentFilter.SYSTEM_HIGH_PRIORITY)
+                    || (priority <= IntentFilter.SYSTEM_LOW_PRIORITY);
+            if (!systemPriority) {
+                final int N = filter.countActions();
+                for (int i = 0; i < N; i++) {
+                    // TODO: expand to additional important broadcasts over time
+                    final String action = filter.getAction(i);
+                    if (action.startsWith("android.intent.action.USER_")
+                            || action.startsWith("android.intent.action.PACKAGE_")
+                            || action.startsWith("android.intent.action.UID_")
+                            || action.startsWith("android.intent.action.EXTERNAL_")
+                            || action.startsWith("android.bluetooth.")
+                            || action.equals(Intent.ACTION_SHUTDOWN)) {
+                        if (DEBUG_BROADCAST) {
+                            Slog.wtf(TAG,
+                                    "System internals registering for " + filter.toLongString()
+                                            + " with app priority; this will race with apps!",
+                                    new Throwable());
+                        }
+
+                        // When undefined, assume that system internals need
+                        // to hear about the event first; they can use
+                        // SYSTEM_LOW_PRIORITY if they need to hear last
+                        if (priority == 0) {
+                            filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+
+        Iterator<String> actions = filter.actionsIterator();
+        if (actions == null) {
+            ArrayList<String> noAction = new ArrayList<String>(1);
+            noAction.add(null);
+            actions = noAction.iterator();
+        }
+        boolean onlyProtectedBroadcasts = true;
+
+        // Collect stickies of users and check if broadcast is only registered for protected
+        // broadcasts
+        int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
+        synchronized (mStickyBroadcasts) {
+            while (actions.hasNext()) {
+                String action = actions.next();
+                for (int id : userIds) {
+                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+                            mStickyBroadcasts.get(id);
+                    if (stickies != null) {
+                        ArrayList<StickyBroadcast> broadcasts = stickies.get(action);
+                        if (broadcasts != null) {
+                            if (stickyBroadcasts == null) {
+                                stickyBroadcasts = new ArrayList<>();
+                            }
+                            stickyBroadcasts.addAll(broadcasts);
+                        }
+                    }
+                }
+                if (onlyProtectedBroadcasts) {
+                    try {
+                        onlyProtectedBroadcasts &=
+                                AppGlobals.getPackageManager().isProtectedBroadcast(action);
+                    } catch (RemoteException e) {
+                        onlyProtectedBroadcasts = false;
+                        Slog.w(TAG, "Remote exception", e);
+                    }
+                }
+            }
+        }
+
+        if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
+            SdkSandboxManagerLocal sdkSandboxManagerLocal =
+                    LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+            if (sdkSandboxManagerLocal == null) {
+                throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
+                        + " whether SDK sandbox uid can register to broadcast receivers.");
+            }
+            if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
+                    /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
+                throw new SecurityException("SDK sandbox not allowed to register receiver"
+                        + " with the given IntentFilter: " + filter.toLongString());
+            }
+        }
+
+        // If the change is enabled, but neither exported or not exported is set, we need to log
+        // an error so the consumer can know to explicitly set the value for their flag.
+        // If the caller is registering for a sticky broadcast with a null receiver, we won't
+        // require a flag
+        final boolean explicitExportStateDefined =
+                (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0;
+        if (((flags & Context.RECEIVER_EXPORTED) != 0) && (
+                (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) {
+            throw new IllegalArgumentException(
+                    "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED"
+                            + "flag");
+        }
+
+        // Don't enforce the flag check if we're EITHER registering for only protected
+        // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
+        // not be used generally, so we will be marking them as exported by default
+        boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+                DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
+
+        // A receiver that is visible to instant apps must also be exported.
+        final boolean unexportedReceiverVisibleToInstantApps =
+                ((flags & Context.RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && (
+                        (flags & Context.RECEIVER_NOT_EXPORTED) != 0);
+        if (unexportedReceiverVisibleToInstantApps && requireExplicitFlagForDynamicReceivers) {
+            throw new IllegalArgumentException(
+                    "Receiver can't specify both RECEIVER_VISIBLE_TO_INSTANT_APPS and "
+                            + "RECEIVER_NOT_EXPORTED flag");
+        }
+
+        if (!onlyProtectedBroadcasts) {
+            if (receiver == null && !explicitExportStateDefined) {
+                // sticky broadcast, no flag specified (flag isn't required)
+                flags |= Context.RECEIVER_EXPORTED;
+            } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
+                throw new SecurityException(
+                        callerPackage + ": One of RECEIVER_EXPORTED or "
+                                + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
+                                + "isn't being registered exclusively for system broadcasts");
+                // Assume default behavior-- flag check is not enforced
+            } else if (!requireExplicitFlagForDynamicReceivers && (
+                    (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
+                // Change is not enabled, assume exported unless otherwise specified.
+                flags |= Context.RECEIVER_EXPORTED;
+            }
+        } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
+            flags |= Context.RECEIVER_EXPORTED;
+        }
+
+        // Dynamic receivers are exported by default for versions prior to T
+        final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0;
+
+        ArrayList<StickyBroadcast> allSticky = null;
+        if (stickyBroadcasts != null) {
+            final ContentResolver resolver = mContext.getContentResolver();
+            // Look for any matching sticky broadcasts...
+            for (int i = 0, N = stickyBroadcasts.size(); i < N; i++) {
+                final StickyBroadcast broadcast = stickyBroadcasts.get(i);
+                Intent intent = broadcast.intent;
+                // Don't provided intents that aren't available to instant apps.
+                if (instantApp && (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
+                        == 0) {
+                    continue;
+                }
+                // If intent has scheme "content", it will need to access
+                // provider that needs to lock mProviderMap in ActivityThread
+                // and also it may need to wait application response, so we
+                // cannot lock ActivityManagerService here.
+                final int match;
+                if (Flags.avoidResolvingType()) {
+                    match = filter.match(intent.getAction(), broadcast.resolvedDataType,
+                            intent.getScheme(), intent.getData(), intent.getCategories(),
+                            TAG, false /* supportsWildcards */, null /* ignoreActions */,
+                            intent.getExtras());
+                } else {
+                    match = filter.match(resolver, intent, true, TAG);
+                }
+                if (match >= 0) {
+                    if (allSticky == null) {
+                        allSticky = new ArrayList<>();
+                    }
+                    allSticky.add(broadcast);
+                }
+            }
+        }
+
+        // The first sticky in the list is returned directly back to the client.
+        Intent sticky = allSticky != null ? allSticky.get(0).intent : null;
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky);
+        if (receiver == null) {
+            return sticky;
+        }
+
+        // SafetyNet logging for b/177931370. If any process other than system_server tries to
+        // listen to this broadcast action, then log it.
+        if (callingPid != Process.myPid()) {
+            if (filter.hasAction("com.android.server.net.action.SNOOZE_WARNING")
+                    || filter.hasAction("com.android.server.net.action.SNOOZE_RAPID")) {
+                EventLog.writeEvent(0x534e4554, "177931370", callingUid, "");
+            }
+        }
+
+        synchronized (mService) {
+            IApplicationThread thread;
+            if (callerApp != null && ((thread = callerApp.getThread()) == null
+                    || thread.asBinder() != caller.asBinder())) {
+                // Original caller already died
+                return null;
+            }
+            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+            if (rl == null) {
+                rl = new ReceiverList(mService, callerApp, callingPid, callingUid,
+                        userId, receiver);
+                if (rl.app != null) {
+                    final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
+                    if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
+                        throw new IllegalStateException("Too many receivers, total of "
+                                + totalReceiversForApp + ", registered for pid: "
+                                + rl.pid + ", callerPackage: " + callerPackage);
+                    }
+                    rl.app.mReceivers.addReceiver(rl);
+                } else {
+                    try {
+                        receiver.asBinder().linkToDeath(rl, 0);
+                    } catch (RemoteException e) {
+                        return sticky;
+                    }
+                    rl.linkedToDeath = true;
+                }
+                mRegisteredReceivers.put(receiver.asBinder(), rl);
+            } else if (rl.uid != callingUid) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for uid " + callingUid
+                                + " was previously registered for uid " + rl.uid
+                                + " callerPackage is " + callerPackage);
+            } else if (rl.pid != callingPid) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for pid " + callingPid
+                                + " was previously registered for pid " + rl.pid
+                                + " callerPackage is " + callerPackage);
+            } else if (rl.userId != userId) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for user " + userId
+                                + " was previously registered for user " + rl.userId
+                                + " callerPackage is " + callerPackage);
+            }
+            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
+                    receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
+                    exported);
+            if (rl.containsFilter(filter)) {
+                Slog.w(TAG, "Receiver with filter " + filter
+                        + " already registered for pid " + rl.pid
+                        + ", callerPackage is " + callerPackage);
+            } else {
+                rl.add(bf);
+                if (!bf.debugCheck()) {
+                    Slog.w(TAG, "==> For Dynamic broadcast");
+                }
+                mReceiverResolver.addFilter(mService.getPackageManagerInternal().snapshot(), bf);
+            }
+
+            // Enqueue broadcasts for all existing stickies that match
+            // this filter.
+            if (allSticky != null) {
+                ArrayList receivers = new ArrayList();
+                receivers.add(bf);
+                sticky = null;
+
+                final int stickyCount = allSticky.size();
+                for (int i = 0; i < stickyCount; i++) {
+                    final StickyBroadcast broadcast = allSticky.get(i);
+                    final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
+                    // TODO(b/281889567): consider using checkComponentPermission instead of
+                    //  canAccessUnexportedComponents
+                    if (sticky == null && (exported || originalStickyCallingUid == callingUid
+                            || ActivityManager.canAccessUnexportedComponents(
+                            originalStickyCallingUid))) {
+                        sticky = broadcast.intent;
+                    }
+                    BroadcastQueue queue = mBroadcastQueue;
+                    BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
+                            null, null, -1, -1, false, null, null, null, null, OP_NONE,
+                            BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
+                            receivers, null, null, 0, null, null, false, true, true, -1,
+                            originalStickyCallingUid, BackgroundStartPrivileges.NONE,
+                            false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+                            null /* filterExtrasForReceiver */,
+                            broadcast.originalCallingAppProcessState);
+                    queue.enqueueBroadcastLocked(r);
+                }
+            }
+
+            return sticky;
+        }
+    }
+
+    void unregisterReceiver(IIntentReceiver receiver) {
+        traceUnregistrationBegin(receiver);
+        try {
+            unregisterReceiverTraced(receiver);
+        } finally {
+            traceUnregistrationEnd();
+        }
+    }
+
+    private static void traceUnregistrationBegin(IIntentReceiver receiver) {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    TextUtils.formatSimple("unregisterReceiver: %d/%s", Binder.getCallingUid(),
+                            receiver == null ? "null" : receiver.asBinder()));
+        }
+    }
+
+    private static void traceUnregistrationEnd() {
+        if (!Flags.traceReceiverRegistration()) {
+            return;
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    private void unregisterReceiverTraced(IIntentReceiver receiver) {
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver);
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            boolean doTrim = false;
+            synchronized (mService) {
+                ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+                if (rl != null) {
+                    final BroadcastRecord r = rl.curBroadcast;
+                    if (r != null) {
+                        final boolean doNext = r.queue.finishReceiverLocked(
+                                rl.app, r.resultCode, r.resultData, r.resultExtras,
+                                r.resultAbort, false);
+                        if (doNext) {
+                            doTrim = true;
+                        }
+                    }
+                    if (rl.app != null) {
+                        rl.app.mReceivers.removeReceiver(rl);
+                    }
+                    removeReceiverLocked(rl);
+                    if (rl.linkedToDeath) {
+                        rl.linkedToDeath = false;
+                        rl.receiver.asBinder().unlinkToDeath(rl, 0);
+                    }
+                }
+
+                // If we actually concluded any broadcasts, we might now be able
+                // to trim the recipients' apps from our working set
+                if (doTrim) {
+                    mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+                    return;
+                }
+            }
+
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    void removeReceiverLocked(ReceiverList rl) {
+        mRegisteredReceivers.remove(rl.receiver.asBinder());
+        for (int i = rl.size() - 1; i >= 0; i--) {
+            mReceiverResolver.removeFilter(rl.get(i));
+        }
+    }
+
+    int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
+            Intent intent, String resolvedType, IIntentReceiver resultTo,
+            int resultCode, String resultData, Bundle resultExtras,
+            String[] requiredPermissions, String[] excludedPermissions,
+            String[] excludedPackages, int appOp, Bundle bOptions,
+            boolean serialized, boolean sticky, int userId) {
+        mService.enforceNotIsolatedCaller("broadcastIntent");
+
+        synchronized (mService) {
+            intent = verifyBroadcastLocked(intent);
+
+            final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+            final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
+
+            // We're delivering the result to the caller
+            final ProcessRecord resultToApp = callerApp;
+
+            // Permission regimes around sender-supplied broadcast options.
+            enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
+
+            final ComponentName cn = intent.getComponent();
+
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "broadcastIntent:" + (cn != null ? cn.toString() : intent.getAction()));
+
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                return broadcastIntentLocked(callerApp,
+                        callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
+                        intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+                        resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+                        appOp, bOptions, serialized, sticky, callingPid, callingUid, callingUid,
+                        callingPid, userId, BackgroundStartPrivileges.NONE, null, null);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            }
+        }
+    }
+
+    // Not the binder call surface
+    int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
+            int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
+            String resultData, Bundle resultExtras, String requiredPermission, Bundle bOptions,
+            boolean serialized, boolean sticky, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList) {
+        synchronized (mService) {
+            intent = verifyBroadcastLocked(intent);
+
+            final long origId = Binder.clearCallingIdentity();
+            String[] requiredPermissions = requiredPermission == null ? null
+                    : new String[] {requiredPermission};
+            try {
+                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+                        resultToApp, resultTo, resultCode, resultData, resultExtras,
+                        requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
+                        uid, realCallingUid, realCallingPid, userId,
+                        backgroundStartPrivileges, broadcastAllowList,
+                        null /* filterExtrasForReceiver */);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @GuardedBy("mService")
+    final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
+            @Nullable String callerFeatureId, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+            Bundle resultExtras, String[] requiredPermissions,
+            String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
+            boolean ordered, boolean sticky, int callingPid, int callingUid,
+            int realCallingUid, int realCallingPid, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+        final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
+                callingUid, realCallingUid, userId);
+        try {
+            final BroadcastSentEventRecord broadcastSentEventRecord =
+                    new BroadcastSentEventRecord();
+            final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
+                    intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
+                    resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
+                    appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
+                    callingPid, callingUid, realCallingUid, realCallingPid, userId,
+                    backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
+                    broadcastSentEventRecord);
+            broadcastSentEventRecord.setResult(res);
+            broadcastSentEventRecord.logToStatsd();
+            return res;
+        } finally {
+            traceBroadcastIntentEnd(cookie);
+        }
+    }
+
+    private static int traceBroadcastIntentBegin(Intent intent, IIntentReceiver resultTo,
+            boolean ordered, boolean sticky, int callingUid, int realCallingUid, int userId) {
+        if (!Flags.traceReceiverRegistration()) {
+            return BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
+        }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            final StringBuilder sb = new StringBuilder("broadcastIntent: ");
+            sb.append(callingUid); sb.append('/');
+            final String action = intent.getAction();
+            sb.append(action == null ? null : action); sb.append('/');
+            sb.append("0x"); sb.append(Integer.toHexString(intent.getFlags())); sb.append('/');
+            sb.append(ordered ? "O" : "_");
+            sb.append(sticky ? "S" : "_");
+            sb.append(resultTo != null ? "C" : "_");
+            sb.append('/');
+            sb.append('u'); sb.append(userId);
+            if (callingUid != realCallingUid) {
+                sb.append('/');
+                sb.append("sender="); sb.append(realCallingUid);
+            }
+            return BroadcastQueue.traceBegin(sb.toString());
+        }
+        return 0;
+    }
+
+    private static void traceBroadcastIntentEnd(int cookie) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            BroadcastQueue.traceEnd(cookie);
+        }
+    }
+
+    @GuardedBy("mService")
+    final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
+            @Nullable String callerFeatureId, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+            Bundle resultExtras, String[] requiredPermissions,
+            String[] excludedPermissions, String[] excludedPackages, int appOp,
+            BroadcastOptions brOptions, boolean ordered, boolean sticky, int callingPid,
+            int callingUid, int realCallingUid, int realCallingPid, int userId,
+            BackgroundStartPrivileges backgroundStartPrivileges,
+            @Nullable int[] broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
+        // Ensure all internal loopers are registered for idle checks
+        BroadcastLoopers.addMyLooper();
+
+        if (Process.isSdkSandboxUid(realCallingUid)) {
+            final SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
+                    SdkSandboxManagerLocal.class);
+            if (sdkSandboxManagerLocal == null) {
+                throw new IllegalStateException("SdkSandboxManagerLocal not found when sending"
+                        + " a broadcast from an SDK sandbox uid.");
+            }
+            if (!sdkSandboxManagerLocal.canSendBroadcast(intent)) {
+                throw new SecurityException(
+                        "Intent " + intent.getAction() + " may not be broadcast from an SDK sandbox"
+                                + " uid. Given caller package " + callerPackage
+                                + " (pid=" + callingPid + ", realCallingUid=" + realCallingUid
+                                + ", callingUid= " + callingUid + ")");
+            }
+        }
+
+        if ((resultTo != null) && (resultToApp == null)) {
+            if (resultTo.asBinder() instanceof BinderProxy) {
+                // Warn when requesting results without a way to deliver them
+                Slog.wtf(TAG, "Sending broadcast " + intent.getAction()
+                        + " with resultTo requires resultToApp", new Throwable());
+            } else {
+                // If not a BinderProxy above, then resultTo is an in-process
+                // receiver, so splice in system_server process
+                resultToApp = mService.getProcessRecordLocked("system", SYSTEM_UID);
+            }
+        }
+
+        intent = new Intent(intent);
+        broadcastSentEventRecord.setIntent(intent);
+        broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
+        broadcastSentEventRecord.setSenderUid(callingUid);
+        broadcastSentEventRecord.setRealSenderUid(realCallingUid);
+        broadcastSentEventRecord.setSticky(sticky);
+        broadcastSentEventRecord.setOrdered(ordered);
+        broadcastSentEventRecord.setResultRequested(resultTo != null);
+        final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
+        broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
+        broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
+                realCallingPid));
+
+        final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
+        // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
+        if (callerInstantApp) {
+            intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+        }
+
+        if (userId == UserHandle.USER_ALL && broadcastAllowList != null) {
+            Slog.e(TAG, "broadcastAllowList only applies when sending to individual users. "
+                    + "Assuming restrictive whitelist.");
+            broadcastAllowList = new int[]{};
+        }
+
+        // By default broadcasts do not go to stopped apps.
+        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+
+        // If we have not finished booting, don't allow this to launch new processes.
+        if (!mService.mProcessesReady
+                && (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        }
+
+        if (DEBUG_BROADCAST_LIGHT) {
+            Slog.v(TAG_BROADCAST,
+                    (sticky ? "Broadcast sticky: " : "Broadcast: ") + intent
+                            + " ordered=" + ordered + " userid=" + userId
+                            + " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
+        }
+        if ((resultTo != null) && !ordered) {
+            if (!UserHandle.isCore(callingUid)) {
+                String msg = "Unauthorized unordered resultTo broadcast "
+                        + intent + " sent from uid " + callingUid;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+        }
+
+        userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
+                ALLOW_NON_FULL, "broadcast", callerPackage);
+
+        // Make sure that the user who is receiving this broadcast or its parent is running.
+        // If not, we will just skip it. Make an exception for shutdown broadcasts, upgrade steps.
+        if (userId != UserHandle.USER_ALL && !mService.mUserController.isUserOrItsParentRunning(
+                userId)) {
+            if ((callingUid != SYSTEM_UID
+                    || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
+                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
+                Slog.w(TAG, "Skipping broadcast of " + intent
+                        + ": user " + userId + " and its parent (if any) are stopped");
+                scheduleCanceledResultTo(resultToApp, resultTo, intent, userId,
+                        brOptions, callingUid, callerPackage);
+                return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
+            }
+        }
+
+        final String action = intent.getAction();
+        if (brOptions != null) {
+            if (brOptions.getTemporaryAppAllowlistDuration() > 0) {
+                // See if the caller is allowed to do this.  Note we are checking against
+                // the actual real caller (not whoever provided the operation as say a
+                // PendingIntent), because that who is actually supplied the arguments.
+                if (checkComponentPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED
+                        && checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED
+                        && checkComponentPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    String msg = "Permission Denial: " + intent.getAction()
+                            + " broadcast from " + callerPackage + " (pid=" + callingPid
+                            + ", uid=" + callingUid + ")"
+                            + " requires "
+                            + CHANGE_DEVICE_IDLE_TEMP_WHITELIST + " or "
+                            + START_ACTIVITIES_FROM_BACKGROUND + " or "
+                            + START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                }
+            }
+            if (brOptions.isDontSendToRestrictedApps()
+                    && !mService.isUidActiveLOSP(callingUid)
+                    && mService.isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
+                Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
+                        + " has background restrictions");
+                return ActivityManager.START_CANCELED;
+            }
+            if (brOptions.allowsBackgroundActivityStarts()) {
+                // See if the caller is allowed to do this.  Note we are checking against
+                // the actual real caller (not whoever provided the operation as say a
+                // PendingIntent), because that who is actually supplied the arguments.
+                if (checkComponentPermission(
+                        android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+                        realCallingPid, realCallingUid, -1, true)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    String msg = "Permission Denial: " + intent.getAction()
+                            + " broadcast from " + callerPackage + " (pid=" + callingPid
+                            + ", uid=" + callingUid + ")"
+                            + " requires "
+                            + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                } else {
+                    // We set the token to null since if it wasn't for it we'd allow anyway here
+                    backgroundStartPrivileges = BackgroundStartPrivileges.ALLOW_BAL;
+                }
+            }
+
+            if (brOptions.getIdForResponseEvent() > 0) {
+                mService.enforcePermission(
+                        android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS,
+                        callingPid, callingUid, "recordResponseEventWhileInBackground");
+            }
+        }
+
+        // Verify that protected broadcasts are only being sent by system code,
+        // and that system code is only sending protected broadcasts.
+        final boolean isProtectedBroadcast;
+        try {
+            isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Remote exception", e);
+            scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                    userId, brOptions, callingUid, callerPackage);
+            return ActivityManager.BROADCAST_SUCCESS;
+        }
+
+        final boolean isCallerSystem;
+        switch (UserHandle.getAppId(callingUid)) {
+            case ROOT_UID:
+            case SYSTEM_UID:
+            case PHONE_UID:
+            case BLUETOOTH_UID:
+            case NFC_UID:
+            case SE_UID:
+            case NETWORK_STACK_UID:
+                isCallerSystem = true;
+                break;
+            default:
+                isCallerSystem = (callerApp != null) && callerApp.isPersistent();
+                break;
+        }
+
+        // First line security check before anything else: stop non-system apps from
+        // sending protected broadcasts.
+        if (!isCallerSystem) {
+            if (isProtectedBroadcast) {
+                String msg = "Permission Denial: not allowed to send broadcast "
+                        + action + " from pid="
+                        + callingPid + ", uid=" + callingUid;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+
+            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+                    || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+                // Special case for compatibility: we don't want apps to send this,
+                // but historically it has not been protected and apps may be using it
+                // to poke their own app widget.  So, instead of making it protected,
+                // just limit it to the caller.
+                if (callerPackage == null) {
+                    String msg = "Permission Denial: not allowed to send broadcast "
+                            + action + " from unknown caller.";
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                } else if (intent.getComponent() != null) {
+                    // They are good enough to send to an explicit component...  verify
+                    // it is being sent to the calling app.
+                    if (!intent.getComponent().getPackageName().equals(
+                            callerPackage)) {
+                        String msg = "Permission Denial: not allowed to send broadcast "
+                                + action + " to "
+                                + intent.getComponent().getPackageName() + " from "
+                                + callerPackage;
+                        Slog.w(TAG, msg);
+                        throw new SecurityException(msg);
+                    }
+                } else {
+                    // Limit broadcast to their own package.
+                    intent.setPackage(callerPackage);
+                }
+            }
+        }
+
+        boolean timeoutExempt = false;
+
+        if (action != null) {
+            if (getBackgroundLaunchBroadcasts().contains(action)) {
+                if (DEBUG_BACKGROUND_CHECK) {
+                    Slog.i(TAG, "Broadcast action " + action + " forcing include-background");
+                }
+                intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            }
+
+            // TODO: b/329211459 - Remove this after background remote intent is fixed.
+            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                    && getWearRemoteIntentAction().equals(action)) {
+                final int callerProcState = callerApp != null
+                        ? callerApp.getCurProcState()
+                        : ActivityManager.PROCESS_STATE_NONEXISTENT;
+                if (ActivityManager.RunningAppProcessInfo.procStateToImportance(callerProcState)
+                        > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                    return ActivityManager.START_CANCELED;
+                }
+            }
+
+            switch (action) {
+                case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE:
+                    UserManagerInternal umInternal = LocalServices.getService(
+                            UserManagerInternal.class);
+                    UserInfo userInfo = umInternal.getUserInfo(userId);
+                    if (userInfo != null && userInfo.isCloneProfile()) {
+                        userId = umInternal.getProfileParentId(userId);
+                    }
+                    break;
+                case Intent.ACTION_UID_REMOVED:
+                case Intent.ACTION_PACKAGE_REMOVED:
+                case Intent.ACTION_PACKAGE_CHANGED:
+                case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+                case Intent.ACTION_PACKAGES_SUSPENDED:
+                case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                    // Handle special intents: if this broadcast is from the package
+                    // manager about a package being removed, we need to remove all of
+                    // its activities from the history stack.
+                    if (checkComponentPermission(
+                            android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
+                            callingPid, callingUid, -1, true)
+                            != PackageManager.PERMISSION_GRANTED) {
+                        String msg = "Permission Denial: " + intent.getAction()
+                                + " broadcast from " + callerPackage + " (pid=" + callingPid
+                                + ", uid=" + callingUid + ")"
+                                + " requires "
+                                + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
+                        Slog.w(TAG, msg);
+                        throw new SecurityException(msg);
+                    }
+                    switch (action) {
+                        case Intent.ACTION_UID_REMOVED:
+                            final int uid = getUidFromIntent(intent);
+                            if (uid >= 0) {
+                                mService.mBatteryStatsService.removeUid(uid);
+                                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                                    mService.mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
+                                            intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
+                                } else {
+                                    mService.mAppOpsService.uidRemoved(uid);
+                                    mService.mServices.onUidRemovedLocked(uid);
+                                }
+                            }
+                            break;
+                        case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                            // If resources are unavailable just force stop all those packages
+                            // and flush the attribute cache as well.
+                            String[] list = intent.getStringArrayExtra(
+                                    Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                            if (list != null && list.length > 0) {
+                                for (int i = 0; i < list.length; i++) {
+                                    mService.forceStopPackageLocked(list[i], -1, false, true, true,
+                                            false, false, false, userId, "storage unmount");
+                                }
+                                mService.mAtmInternal.cleanupRecentTasksForUser(
+                                        UserHandle.USER_ALL);
+                                sendPackageBroadcastLocked(
+                                        ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE,
+                                        list, userId);
+                            }
+                            break;
+                        case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+                            mService.mAtmInternal.cleanupRecentTasksForUser(UserHandle.USER_ALL);
+                            break;
+                        case Intent.ACTION_PACKAGE_REMOVED:
+                        case Intent.ACTION_PACKAGE_CHANGED:
+                            Uri data = intent.getData();
+                            String ssp;
+                            if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                                boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
+                                final boolean replacing =
+                                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                                final boolean killProcess =
+                                        !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
+                                final boolean fullUninstall = removed && !replacing;
+
+                                if (removed) {
+                                    if (killProcess) {
+                                        mService.forceStopPackageLocked(ssp, UserHandle.getAppId(
+                                                        intent.getIntExtra(Intent.EXTRA_UID, -1)),
+                                                false, true, true, false, fullUninstall, false,
+                                                userId, "pkg removed");
+                                        mService.getPackageManagerInternal()
+                                                .onPackageProcessKilledForUninstall(ssp);
+                                    } else {
+                                        // Kill any app zygotes always, since they can't fork new
+                                        // processes with references to the old code
+                                        mService.forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
+                                                        intent.getIntExtra(Intent.EXTRA_UID, -1)),
+                                                userId);
+                                    }
+                                    final int cmd = killProcess
+                                            ? ApplicationThreadConstants.PACKAGE_REMOVED
+                                            : ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL;
+                                    sendPackageBroadcastLocked(cmd,
+                                            new String[] {ssp}, userId);
+                                    if (fullUninstall) {
+                                        // Remove all permissions granted from/to this package
+                                        mService.mUgmInternal.removeUriPermissionsForPackage(ssp,
+                                                userId, true, false);
+
+                                        mService.mAtmInternal.removeRecentTasksByPackageName(ssp,
+                                                userId);
+
+                                        mService.mServices.forceStopPackageLocked(ssp, userId);
+                                        mService.mAtmInternal.onPackageUninstalled(ssp, userId);
+                                        mService.mBatteryStatsService.notePackageUninstalled(ssp);
+                                    }
+                                } else {
+                                    if (killProcess) {
+                                        int reason;
+                                        int subReason;
+                                        if (replacing) {
+                                            reason = ApplicationExitInfo.REASON_PACKAGE_UPDATED;
+                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+                                        } else {
+                                            reason =
+                                                    ApplicationExitInfo.REASON_PACKAGE_STATE_CHANGE;
+                                            subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
+                                        }
+
+                                        final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
+                                                -1);
+                                        synchronized (mService.mProcLock) {
+                                            mService.mProcessList.killPackageProcessesLSP(ssp,
+                                                    UserHandle.getAppId(extraUid),
+                                                    userId, ProcessList.INVALID_ADJ,
+                                                    reason,
+                                                    subReason,
+                                                    "change " + ssp);
+                                        }
+                                    }
+                                    mService.cleanupDisabledPackageComponentsLocked(ssp, userId,
+                                            intent.getStringArrayExtra(
+                                                    Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
+                                    mService.mServices.schedulePendingServiceStartLocked(
+                                            ssp, userId);
+                                }
+                            }
+                            break;
+                        case Intent.ACTION_PACKAGES_SUSPENDED:
+                        case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                            final boolean suspended = Intent.ACTION_PACKAGES_SUSPENDED.equals(
+                                    intent.getAction());
+                            final String[] packageNames = intent.getStringArrayExtra(
+                                    Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                            final int userIdExtra = intent.getIntExtra(
+                                    Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
+                            mService.mAtmInternal.onPackagesSuspendedChanged(packageNames,
+                                    suspended, userIdExtra);
+
+                            final boolean quarantined = intent.getBooleanExtra(
+                                    Intent.EXTRA_QUARANTINED, false);
+                            if (suspended && quarantined && packageNames != null) {
+                                for (int i = 0; i < packageNames.length; i++) {
+                                    mService.forceStopPackage(packageNames[i], userId,
+                                            ActivityManager.FLAG_OR_STOPPED, "quarantined");
+                                }
+                            }
+
+                            break;
+                    }
+                    break;
+                case Intent.ACTION_PACKAGE_REPLACED: {
+                    final Uri data = intent.getData();
+                    final String ssp;
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                        ApplicationInfo aInfo = null;
+                        try {
+                            aInfo = AppGlobals.getPackageManager()
+                                    .getApplicationInfo(ssp, STOCK_PM_FLAGS, userId);
+                        } catch (RemoteException ignore) {
+                        }
+                        if (aInfo == null) {
+                            Slog.w(TAG, "Dropping ACTION_PACKAGE_REPLACED for non-existent pkg:"
+                                    + " ssp=" + ssp + " data=" + data);
+                            scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                                    userId, brOptions, callingUid, callerPackage);
+                            return ActivityManager.BROADCAST_SUCCESS;
+                        }
+                        mService.updateAssociationForApp(aInfo);
+                        mService.mAtmInternal.onPackageReplaced(aInfo);
+                        mService.mServices.updateServiceApplicationInfoLocked(aInfo);
+                        sendPackageBroadcastLocked(ApplicationThreadConstants.PACKAGE_REPLACED,
+                                new String[] {ssp}, userId);
+                    }
+                    break;
+                }
+                case Intent.ACTION_PACKAGE_ADDED: {
+                    // Special case for adding a package: by default turn on compatibility mode.
+                    Uri data = intent.getData();
+                    String ssp;
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                        final boolean replacing =
+                                intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                        mService.mAtmInternal.onPackageAdded(ssp, replacing);
+
+                        try {
+                            ApplicationInfo ai = AppGlobals.getPackageManager()
+                                    .getApplicationInfo(ssp, STOCK_PM_FLAGS, 0);
+                            mService.mBatteryStatsService.notePackageInstalled(ssp,
+                                    ai != null ? ai.longVersionCode : 0);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                    break;
+                }
+                case Intent.ACTION_PACKAGE_DATA_CLEARED: {
+                    Uri data = intent.getData();
+                    String ssp;
+                    if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
+                        mService.mAtmInternal.onPackageDataCleared(ssp, userId);
+                    }
+                    break;
+                }
+                case Intent.ACTION_TIMEZONE_CHANGED:
+                    // If this is the time zone changed action, queue up a message that will reset
+                    // the timezone of all currently running processes. This message will get
+                    // queued up before the broadcast happens.
+                    mService.mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
+                    break;
+                case Intent.ACTION_TIME_CHANGED:
+                    // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
+                    // the tri-state value it may contain and "unknown".
+                    // For convenience we re-use the Intent extra values.
+                    final int NO_EXTRA_VALUE_FOUND = -1;
+                    final int timeFormatPreferenceMsgValue = intent.getIntExtra(
+                            Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+                            NO_EXTRA_VALUE_FOUND /* defaultValue */);
+                    // Only send a message if the time preference is available.
+                    if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
+                        Message updateTimePreferenceMsg =
+                                mService.mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
+                                        timeFormatPreferenceMsgValue, 0);
+                        mService.mHandler.sendMessage(updateTimePreferenceMsg);
+                    }
+                    mService.mBatteryStatsService.noteCurrentTimeChanged();
+                    break;
+                case ConnectivityManager.ACTION_CLEAR_DNS_CACHE:
+                    mService.mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
+                    break;
+                case Proxy.PROXY_CHANGE_ACTION:
+                    mService.mHandler.sendMessage(mService.mHandler.obtainMessage(
+                            UPDATE_HTTP_PROXY_MSG));
+                    break;
+                case android.hardware.Camera.ACTION_NEW_PICTURE:
+                case android.hardware.Camera.ACTION_NEW_VIDEO:
+                    // In N we just turned these off; in O we are turing them back on partly,
+                    // only for registered receivers.  This will still address the main problem
+                    // (a spam of apps waking up when a picture is taken putting significant
+                    // memory pressure on the system at a bad point), while still allowing apps
+                    // that are already actively running to know about this happening.
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    break;
+                case android.security.KeyChain.ACTION_TRUST_STORE_CHANGED:
+                    mService.mHandler.sendEmptyMessage(HANDLE_TRUST_STORAGE_UPDATE_MSG);
+                    break;
+                case "com.android.launcher.action.INSTALL_SHORTCUT":
+                    // As of O, we no longer support this broadcasts, even for pre-O apps.
+                    // Apps should now be using ShortcutManager.pinRequestShortcut().
+                    Log.w(TAG, "Broadcast " + action
+                            + " no longer supported. It will not be delivered.");
+                    scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                            userId, brOptions, callingUid, callerPackage);
+                    return ActivityManager.BROADCAST_SUCCESS;
+                case Intent.ACTION_PRE_BOOT_COMPLETED:
+                    timeoutExempt = true;
+                    break;
+                case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
+                    if (!mService.mAtmInternal.checkCanCloseSystemDialogs(callingPid, callingUid,
+                            callerPackage)) {
+                        scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                                userId, brOptions, callingUid, callerPackage);
+                        // Returning success seems to be the pattern here
+                        return ActivityManager.BROADCAST_SUCCESS;
+                    }
+                    break;
+            }
+
+            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+                    || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                    || Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+                final int uid = getUidFromIntent(intent);
+                if (uid != -1) {
+                    final UidRecord uidRec = mService.mProcessList.getUidRecordLOSP(uid);
+                    if (uidRec != null) {
+                        uidRec.updateHasInternetPermission();
+                    }
+                }
+            }
+        }
+
+        // Add to the sticky list if requested.
+        if (sticky) {
+            if (mService.checkPermission(android.Manifest.permission.BROADCAST_STICKY,
+                    callingPid, callingUid)
+                    != PackageManager.PERMISSION_GRANTED) {
+                String msg =
+                        "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
+                                + " pid="
+                                + callingPid
+                                + ", uid="
+                                + callingUid
+                                + " requires "
+                                + android.Manifest.permission.BROADCAST_STICKY;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+            if (requiredPermissions != null && requiredPermissions.length > 0) {
+                Slog.w(TAG, "Can't broadcast sticky intent " + intent
+                        + " and enforce permissions " + Arrays.toString(requiredPermissions));
+                scheduleCanceledResultTo(resultToApp, resultTo, intent,
+                        userId, brOptions, callingUid, callerPackage);
+                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
+            }
+            if (intent.getComponent() != null) {
+                throw new SecurityException(
+                        "Sticky broadcasts can't target a specific component");
+            }
+            synchronized (mStickyBroadcasts) {
+                // We use userId directly here, since the "all" target is maintained
+                // as a separate set of sticky broadcasts.
+                if (userId != UserHandle.USER_ALL) {
+                    // But first, if this is not a broadcast to all users, then
+                    // make sure it doesn't conflict with an existing broadcast to
+                    // all users.
+                    ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(
+                            UserHandle.USER_ALL);
+                    if (stickies != null) {
+                        ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+                        if (list != null) {
+                            int N = list.size();
+                            int i;
+                            for (i = 0; i < N; i++) {
+                                if (intent.filterEquals(list.get(i).intent)) {
+                                    throw new IllegalArgumentException("Sticky broadcast " + intent
+                                            + " for user " + userId
+                                            + " conflicts with existing global broadcast");
+                                }
+                            }
+                        }
+                    }
+                }
+                ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+                        mStickyBroadcasts.get(userId);
+                if (stickies == null) {
+                    stickies = new ArrayMap<>();
+                    mStickyBroadcasts.put(userId, stickies);
+                }
+                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+                if (list == null) {
+                    list = new ArrayList<>();
+                    stickies.put(intent.getAction(), list);
+                }
+                final boolean deferUntilActive = BroadcastRecord.calculateDeferUntilActive(
+                        callingUid, brOptions, resultTo, ordered,
+                        BroadcastRecord.calculateUrgent(intent, brOptions));
+                final int stickiesCount = list.size();
+                int i;
+                for (i = 0; i < stickiesCount; i++) {
+                    if (intent.filterEquals(list.get(i).intent)) {
+                        // This sticky already exists, replace it.
+                        list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
+                                callingUid, callerAppProcessState, resolvedType));
+                        break;
+                    }
+                }
+                if (i >= stickiesCount) {
+                    list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
+                            callingUid, callerAppProcessState, resolvedType));
+                }
+            }
+        }
+
+        int[] users;
+        if (userId == UserHandle.USER_ALL) {
+            // Caller wants broadcast to go to all started users.
+            users = mService.mUserController.getStartedUserArray();
+        } else {
+            // Caller wants broadcast to go to one specific user.
+            users = new int[] {userId};
+        }
+
+        var args = new SaferIntentUtils.IntentArgs(intent, resolvedType,
+                true /* isReceiver */, true /* resolveForStart */, callingUid, callingPid);
+        args.platformCompat = mService.mPlatformCompat;
+
+        // Figure out who all will receive this broadcast.
+        final int cookie = BroadcastQueue.traceBegin("queryReceivers");
+        List receivers = null;
+        List<BroadcastFilter> registeredReceivers = null;
+        // Need to resolve the intent to interested receivers...
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+            receivers = collectReceiverComponents(
+                    intent, resolvedType, callingUid, callingPid, users, broadcastAllowList);
+        }
+        if (intent.getComponent() == null) {
+            final PackageDataSnapshot snapshot = mService.getPackageManagerInternal().snapshot();
+            if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
+                // Query one target user at a time, excluding shell-restricted users
+                for (int i = 0; i < users.length; i++) {
+                    if (mService.mUserController.hasUserRestriction(
+                            UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
+                        continue;
+                    }
+                    List<BroadcastFilter> registeredReceiversForUser =
+                            mReceiverResolver.queryIntent(snapshot, intent,
+                                    resolvedType, false /*defaultOnly*/, users[i]);
+                    if (registeredReceivers == null) {
+                        registeredReceivers = registeredReceiversForUser;
+                    } else if (registeredReceiversForUser != null) {
+                        registeredReceivers.addAll(registeredReceiversForUser);
+                    }
+                }
+            } else {
+                registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
+                        resolvedType, false /*defaultOnly*/, userId);
+            }
+            if (registeredReceivers != null) {
+                SaferIntentUtils.blockNullAction(args, registeredReceivers);
+            }
+        }
+        BroadcastQueue.traceEnd(cookie);
+
+        final boolean replacePending =
+                (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
+
+        if (DEBUG_BROADCAST) {
+            Slog.v(TAG_BROADCAST, "Enqueueing broadcast: " + intent.getAction()
+                    + " replacePending=" + replacePending);
+        }
+        if (registeredReceivers != null && broadcastAllowList != null) {
+            // if a uid whitelist was provided, remove anything in the application space that wasn't
+            // in it.
+            for (int i = registeredReceivers.size() - 1; i >= 0; i--) {
+                final int owningAppId = UserHandle.getAppId(registeredReceivers.get(i).owningUid);
+                if (owningAppId >= Process.FIRST_APPLICATION_UID
+                        && Arrays.binarySearch(broadcastAllowList, owningAppId) < 0) {
+                    registeredReceivers.remove(i);
+                }
+            }
+        }
+
+        int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
+
+        // Merge into one list.
+        int ir = 0;
+        if (receivers != null) {
+            // A special case for PACKAGE_ADDED: do not allow the package
+            // being added to see this broadcast.  This prevents them from
+            // using this as a back door to get run as soon as they are
+            // installed.  Maybe in the future we want to have a special install
+            // broadcast or such for apps, but we'd like to deliberately make
+            // this decision.
+            String[] skipPackages = null;
+            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
+                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
+                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
+                Uri data = intent.getData();
+                if (data != null) {
+                    String pkgName = data.getSchemeSpecificPart();
+                    if (pkgName != null) {
+                        skipPackages = new String[] { pkgName };
+                    }
+                }
+            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
+                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+            }
+            if (skipPackages != null && (skipPackages.length > 0)) {
+                for (String skipPackage : skipPackages) {
+                    if (skipPackage != null) {
+                        int NT = receivers.size();
+                        for (int it = 0; it < NT; it++) {
+                            ResolveInfo curt = (ResolveInfo) receivers.get(it);
+                            if (curt.activityInfo.packageName.equals(skipPackage)) {
+                                receivers.remove(it);
+                                it--;
+                                NT--;
+                            }
+                        }
+                    }
+                }
+            }
+
+            int NT = receivers != null ? receivers.size() : 0;
+            int it = 0;
+            ResolveInfo curt = null;
+            BroadcastFilter curr = null;
+            while (it < NT && ir < NR) {
+                if (curt == null) {
+                    curt = (ResolveInfo) receivers.get(it);
+                }
+                if (curr == null) {
+                    curr = registeredReceivers.get(ir);
+                }
+                if (curr.getPriority() >= curt.priority) {
+                    // Insert this broadcast record into the final list.
+                    receivers.add(it, curr);
+                    ir++;
+                    curr = null;
+                    it++;
+                    NT++;
+                } else {
+                    // Skip to the next ResolveInfo in the final list.
+                    it++;
+                    curt = null;
+                }
+            }
+        }
+        while (ir < NR) {
+            if (receivers == null) {
+                receivers = new ArrayList();
+            }
+            receivers.add(registeredReceivers.get(ir));
+            ir++;
+        }
+
+        if (isCallerSystem) {
+            checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
+                    isProtectedBroadcast, receivers);
+        }
+
+        if ((receivers != null && receivers.size() > 0)
+                || resultTo != null) {
+            BroadcastQueue queue = mBroadcastQueue;
+            SaferIntentUtils.filterNonExportedComponents(args, receivers);
+            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
+                    callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
+                    requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
+                    receivers, resultToApp, resultTo, resultCode, resultData, resultExtras,
+                    ordered, sticky, false, userId,
+                    backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
+                    callerAppProcessState);
+            broadcastSentEventRecord.setBroadcastRecord(r);
+
+            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
+            queue.enqueueBroadcastLocked(r);
+        } else {
+            // There was nobody interested in the broadcast, but we still want to record
+            // that it happened.
+            if (intent.getComponent() == null && intent.getPackage() == null
+                    && (intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                // This was an implicit broadcast... let's record it for posterity.
+                addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
+            }
+        }
+
+        return ActivityManager.BROADCAST_SUCCESS;
+    }
+
+    @GuardedBy("mService")
+    private void scheduleCanceledResultTo(ProcessRecord resultToApp, IIntentReceiver resultTo,
+            Intent intent, int userId, BroadcastOptions options, int callingUid,
+            String callingPackage) {
+        if (resultTo == null) {
+            return;
+        }
+        final ProcessRecord app = resultToApp;
+        final IApplicationThread thread  = (app != null) ? app.getOnewayThread() : null;
+        if (thread != null) {
+            try {
+                final boolean shareIdentity = (options != null && options.isShareIdentityEnabled());
+                thread.scheduleRegisteredReceiver(
+                        resultTo, intent, Activity.RESULT_CANCELED, null, null,
+                        false, false, true, userId, app.mState.getReportedProcState(),
+                        shareIdentity ? callingUid : Process.INVALID_UID,
+                        shareIdentity ? callingPackage : null);
+            } catch (RemoteException e) {
+                final String msg = "Failed to schedule result of " + intent + " via "
+                        + app + ": " + e;
+                app.killLocked("Can't schedule resultTo", ApplicationExitInfo.REASON_OTHER,
+                        ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
+                Slog.d(TAG, msg);
+            }
+        }
+    }
+
+    @GuardedBy("mService")
+    private int getRealProcessStateLocked(ProcessRecord app, int pid) {
+        if (app == null) {
+            synchronized (mService.mPidsSelfLocked) {
+                app = mService.mPidsSelfLocked.get(pid);
+            }
+        }
+        if (app != null && app.getThread() != null && !app.isKilled()) {
+            return app.mState.getCurProcState();
+        }
+        return PROCESS_STATE_NONEXISTENT;
+    }
+
+    @GuardedBy("mService")
+    private int getRealUidStateLocked(ProcessRecord app, int pid) {
+        if (app == null) {
+            synchronized (mService.mPidsSelfLocked) {
+                app = mService.mPidsSelfLocked.get(pid);
+            }
+        }
+        if (app != null && app.getThread() != null && !app.isKilled()) {
+            final UidRecord uidRecord = app.getUidRecord();
+            if (uidRecord != null) {
+                return uidRecord.getCurProcState();
+            }
+        }
+        return PROCESS_STATE_NONEXISTENT;
+    }
+
+    @VisibleForTesting
+    ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
+        synchronized (mStickyBroadcasts) {
+            final ArrayMap<String, ArrayList<StickyBroadcast>> stickyBroadcasts =
+                    mStickyBroadcasts.get(userId);
+            if (stickyBroadcasts == null) {
+                return null;
+            }
+            return stickyBroadcasts.get(action);
+        }
+    }
+
+    void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        userId = mService.mUserController.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, true, ALLOW_NON_FULL,
+                "removeStickyBroadcast", null);
+
+        if (mService.checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: unbroadcastIntent() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        synchronized (mStickyBroadcasts) {
+            ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
+            if (stickies != null) {
+                ArrayList<StickyBroadcast> list = stickies.get(intent.getAction());
+                if (list != null) {
+                    int N = list.size();
+                    int i;
+                    for (i = 0; i < N; i++) {
+                        if (intent.filterEquals(list.get(i).intent)) {
+                            list.remove(i);
+                            break;
+                        }
+                    }
+                    if (list.size() <= 0) {
+                        stickies.remove(intent.getAction());
+                    }
+                }
+                if (stickies.size() <= 0) {
+                    mStickyBroadcasts.remove(userId);
+                }
+            }
+        }
+    }
+
+    void finishReceiver(IBinder caller, int resultCode, String resultData,
+            Bundle resultExtras, boolean resultAbort, int flags) {
+        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + caller);
+
+        // Refuse possible leaked file descriptors
+        if (resultExtras != null && resultExtras.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Bundle");
+        }
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mService) {
+                final ProcessRecord callerApp = mService.getRecordForAppLOSP(caller);
+                if (callerApp == null) {
+                    Slog.w(TAG, "finishReceiver: no app for " + caller);
+                    return;
+                }
+
+                mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
+                        resultData, resultExtras, resultAbort, true);
+                // updateOomAdjLocked() will be done here
+                mService.trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
+            }
+
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    /**
+     * @return uid from the extra field {@link Intent#EXTRA_UID} if present, Otherwise -1
+     */
+    private int getUidFromIntent(Intent intent) {
+        if (intent == null) {
+            return -1;
+        }
+        final Bundle intentExtras = intent.getExtras();
+        return intent.hasExtra(Intent.EXTRA_UID)
+                ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
+    }
+
+    final void rotateBroadcastStatsIfNeededLocked() {
+        final long now = SystemClock.elapsedRealtime();
+        if (mCurBroadcastStats == null
+                || (mCurBroadcastStats.mStartRealtime + (24 * 60 * 60 * 1000) < now)) {
+            mLastBroadcastStats = mCurBroadcastStats;
+            if (mLastBroadcastStats != null) {
+                mLastBroadcastStats.mEndRealtime = SystemClock.elapsedRealtime();
+                mLastBroadcastStats.mEndUptime = SystemClock.uptimeMillis();
+            }
+            mCurBroadcastStats = new BroadcastStats();
+        }
+    }
+
+    final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+            int skipCount, long dispatchTime) {
+        rotateBroadcastStatsIfNeededLocked();
+        mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
+    }
+
+    final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+        rotateBroadcastStatsIfNeededLocked();
+        mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
+    }
+
+    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+        final String callerPackage = info != null ? info.packageName : original.callerPackage;
+        if (callerPackage != null) {
+            mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+                    original.callingUid, 0, callerPackage).sendToTarget();
+        }
+    }
+
+    final Intent verifyBroadcastLocked(Intent intent) {
+        if (intent != null) {
+            // Refuse possible leaked file descriptors
+            if (intent.hasFileDescriptors()) {
+                throw new IllegalArgumentException("File descriptors passed in Intent");
+            }
+            // Remove existing mismatch flag so it can be properly updated later
+            intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+        }
+
+        int flags = intent.getFlags();
+
+        if (!mService.mProcessesReady) {
+            // if the caller really truly claims to know what they're doing, go
+            // ahead and allow the broadcast without launching any receivers
+            if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
+                // This will be turned into a FLAG_RECEIVER_REGISTERED_ONLY later on if needed.
+            } else if ((flags & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
+                        + " before boot completion");
+                throw new IllegalStateException("Cannot broadcast before boot completed");
+            }
+        }
+
+        if ((flags & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+            throw new IllegalArgumentException(
+                    "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+        }
+
+        if ((flags & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+            switch (Binder.getCallingUid()) {
+                case ROOT_UID:
+                case SHELL_UID:
+                    break;
+                default:
+                    Slog.w(TAG, "Removing FLAG_RECEIVER_FROM_SHELL because caller is UID "
+                            + Binder.getCallingUid());
+                    intent.removeFlags(Intent.FLAG_RECEIVER_FROM_SHELL);
+                    break;
+            }
+        }
+
+        return intent;
+    }
+
+    private ArraySet<String> getBackgroundLaunchBroadcasts() {
+        if (mBackgroundLaunchBroadcasts == null) {
+            mBackgroundLaunchBroadcasts = SystemConfig.getInstance().getAllowImplicitBroadcasts();
+        }
+        return mBackgroundLaunchBroadcasts;
+    }
+
+    private boolean isInstantApp(ProcessRecord record, @Nullable String callerPackage, int uid) {
+        if (UserHandle.getAppId(uid) < FIRST_APPLICATION_UID) {
+            return false;
+        }
+        // Easy case -- we have the app's ProcessRecord.
+        if (record != null) {
+            return record.info.isInstantApp();
+        }
+        // Otherwise check with PackageManager.
+        IPackageManager pm = AppGlobals.getPackageManager();
+        try {
+            if (callerPackage == null) {
+                final String[] packageNames = pm.getPackagesForUid(uid);
+                if (packageNames == null || packageNames.length == 0) {
+                    throw new IllegalArgumentException("Unable to determine caller package name");
+                }
+                // Instant Apps can't use shared uids, so its safe to only check the first package.
+                callerPackage = packageNames[0];
+            }
+            mService.mAppOpsService.checkPackage(uid, callerPackage);
+            return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid));
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e);
+            return true;
+        }
+    }
+
+    private String getWearRemoteIntentAction() {
+        return mContext.getResources().getString(
+                com.android.internal.R.string.config_wearRemoteIntentAction);
+    }
+
+    private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
+        mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
+    }private List<ResolveInfo> collectReceiverComponents(
+            Intent intent, String resolvedType, int callingUid, int callingPid,
+            int[] users, int[] broadcastAllowList) {
+        // TODO: come back and remove this assumption to triage all broadcasts
+        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;
+
+        List<ResolveInfo> receivers = null;
+        HashSet<ComponentName> singleUserReceivers = null;
+        boolean scannedFirstReceivers = false;
+        for (int user : users) {
+            // Skip users that have Shell restrictions
+            if (callingUid == SHELL_UID
+                    && mService.mUserController.hasUserRestriction(
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
+                continue;
+            }
+            List<ResolveInfo> newReceivers = mService.mPackageManagerInt.queryIntentReceivers(
+                    intent, resolvedType, pmFlags, callingUid, callingPid, user, /* forSend */true);
+            if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
+                // If this is not the system user, we need to check for
+                // any receivers that should be filtered out.
+                for (int i = 0; i < newReceivers.size(); i++) {
+                    ResolveInfo ri = newReceivers.get(i);
+                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
+                        newReceivers.remove(i);
+                        i--;
+                    }
+                }
+            }
+            // Replace the alias receivers with their targets.
+            if (newReceivers != null) {
+                for (int i = newReceivers.size() - 1; i >= 0; i--) {
+                    final ResolveInfo ri = newReceivers.get(i);
+                    final ComponentAliasResolver.Resolution<ResolveInfo> resolution =
+                            mService.mComponentAliasResolver.resolveReceiver(intent, ri,
+                                    resolvedType, pmFlags, user, callingUid, callingPid);
+                    if (resolution == null) {
+                        // It was an alias, but the target was not found.
+                        newReceivers.remove(i);
+                        continue;
+                    }
+                    if (resolution.isAlias()) {
+                        newReceivers.set(i, resolution.getTarget());
+                    }
+                }
+            }
+            if (newReceivers != null && newReceivers.size() == 0) {
+                newReceivers = null;
+            }
+
+            if (receivers == null) {
+                receivers = newReceivers;
+            } else if (newReceivers != null) {
+                // We need to concatenate the additional receivers
+                // found with what we have do far.  This would be easy,
+                // but we also need to de-dup any receivers that are
+                // singleUser.
+                if (!scannedFirstReceivers) {
+                    // Collect any single user receivers we had already retrieved.
+                    scannedFirstReceivers = true;
+                    for (int i = 0; i < receivers.size(); i++) {
+                        ResolveInfo ri = receivers.get(i);
+                        if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                            ComponentName cn = new ComponentName(
+                                    ri.activityInfo.packageName, ri.activityInfo.name);
+                            if (singleUserReceivers == null) {
+                                singleUserReceivers = new HashSet<ComponentName>();
+                            }
+                            singleUserReceivers.add(cn);
+                        }
+                    }
+                }
+                // Add the new results to the existing results, tracking
+                // and de-dupping single user receivers.
+                for (int i = 0; i < newReceivers.size(); i++) {
+                    ResolveInfo ri = newReceivers.get(i);
+                    if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                        ComponentName cn = new ComponentName(
+                                ri.activityInfo.packageName, ri.activityInfo.name);
+                        if (singleUserReceivers == null) {
+                            singleUserReceivers = new HashSet<ComponentName>();
+                        }
+                        if (!singleUserReceivers.contains(cn)) {
+                            singleUserReceivers.add(cn);
+                            receivers.add(ri);
+                        }
+                    } else {
+                        receivers.add(ri);
+                    }
+                }
+            }
+        }
+        if (receivers != null && broadcastAllowList != null) {
+            for (int i = receivers.size() - 1; i >= 0; i--) {
+                final int receiverAppId = UserHandle.getAppId(
+                        receivers.get(i).activityInfo.applicationInfo.uid);
+                if (receiverAppId >= Process.FIRST_APPLICATION_UID
+                        && Arrays.binarySearch(broadcastAllowList, receiverAppId) < 0) {
+                    receivers.remove(i);
+                }
+            }
+        }
+        return receivers;
+    }
+
+    private void checkBroadcastFromSystem(Intent intent, ProcessRecord callerApp,
+            String callerPackage, int callingUid, boolean isProtectedBroadcast, List receivers) {
+        if ((intent.getFlags() & Intent.FLAG_RECEIVER_FROM_SHELL) != 0) {
+            // Don't yell about broadcasts sent via shell
+            return;
+        }
+
+        final String action = intent.getAction();
+        if (isProtectedBroadcast
+                || Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+                || Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(action)
+                || Intent.ACTION_MEDIA_BUTTON.equals(action)
+                || Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)
+                || Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(action)
+                || Intent.ACTION_MASTER_CLEAR.equals(action)
+                || Intent.ACTION_FACTORY_RESET.equals(action)
+                || AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
+                || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
+                || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
+                || SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
+                || AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
+                || AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
+            // Broadcast is either protected, or it's a public action that
+            // we've relaxed, so it's fine for system internals to send.
+            return;
+        }
+
+        // This broadcast may be a problem...  but there are often system components that
+        // want to send an internal broadcast to themselves, which is annoying to have to
+        // explicitly list each action as a protected broadcast, so we will check for that
+        // one safe case and allow it: an explicit broadcast, only being received by something
+        // that has protected itself.
+        if (intent.getPackage() != null || intent.getComponent() != null) {
+            if (receivers == null || receivers.size() == 0) {
+                // Intent is explicit and there's no receivers.
+                // This happens, e.g. , when a system component sends a broadcast to
+                // its own runtime receiver, and there's no manifest receivers for it,
+                // because this method is called twice for each broadcast,
+                // for runtime receivers and manifest receivers and the later check would find
+                // no receivers.
+                return;
+            }
+            boolean allProtected = true;
+            for (int i = receivers.size() - 1; i >= 0; i--) {
+                Object target = receivers.get(i);
+                if (target instanceof ResolveInfo) {
+                    ResolveInfo ri = (ResolveInfo) target;
+                    if (ri.activityInfo.exported && ri.activityInfo.permission == null) {
+                        allProtected = false;
+                        break;
+                    }
+                } else {
+                    BroadcastFilter bf = (BroadcastFilter) target;
+                    if (bf.exported && bf.requiredPermission == null) {
+                        allProtected = false;
+                        break;
+                    }
+                }
+            }
+            if (allProtected) {
+                // All safe!
+                return;
+            }
+        }
+
+        // The vast majority of broadcasts sent from system internals
+        // should be protected to avoid security holes, so yell loudly
+        // to ensure we examine these cases.
+        if (callerApp != null) {
+            Log.wtf(TAG, "Sending non-protected broadcast " + action
+                            + " from system " + callerApp.toShortString() + " pkg " + callerPackage,
+                    new Throwable());
+        } else {
+            Log.wtf(TAG, "Sending non-protected broadcast " + action
+                            + " from system uid " + UserHandle.formatUid(callingUid)
+                            + " pkg " + callerPackage,
+                    new Throwable());
+        }
+    }
+
+    // Apply permission policy around the use of specific broadcast options
+    void enforceBroadcastOptionPermissionsInternal(
+            @Nullable Bundle options, int callingUid) {
+        enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
+                callingUid);
+    }
+
+    private void enforceBroadcastOptionPermissionsInternal(
+            @Nullable BroadcastOptions options, int callingUid) {
+        if (options != null && callingUid != Process.SYSTEM_UID) {
+            if (options.isAlarmBroadcast()) {
+                if (DEBUG_BROADCAST_LIGHT) {
+                    Slog.w(TAG, "Non-system caller " + callingUid
+                            + " may not flag broadcast as alarm");
+                }
+                throw new SecurityException(
+                        "Non-system callers may not flag broadcasts as alarm");
+            }
+            if (options.isInteractive()) {
+                mService.enforceCallingPermission(
+                        android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
+                        "setInteractive");
+            }
+        }
+    }
+
+    void startBroadcastObservers() {
+        mBroadcastQueue.start(mContext.getContentResolver());
+    }
+
+    void removeStickyBroadcasts(int userId) {
+        synchronized (mStickyBroadcasts) {
+            mStickyBroadcasts.remove(userId);
+        }
+    }
+
+    @NeverCompile
+    void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        boolean dumpConstants = true;
+        boolean dumpHistory = true;
+        boolean needSep = false;
+        boolean onlyHistory = false;
+        boolean printedAnything = false;
+        boolean onlyReceivers = false;
+        int filteredUid = Process.INVALID_UID;
+
+        if ("history".equals(dumpPackage)) {
+            if (opti < args.length && "-s".equals(args[opti])) {
+                dumpAll = false;
+            }
+            onlyHistory = true;
+            dumpPackage = null;
+        }
+        if ("receivers".equals(dumpPackage)) {
+            onlyReceivers = true;
+            dumpPackage = null;
+            if (opti + 2 <= args.length) {
+                for (int i = opti; i < args.length; i++) {
+                    String arg = args[i];
+                    switch (arg) {
+                        case "--uid":
+                            filteredUid = getIntArg(pw, args, ++i, Process.INVALID_UID);
+                            if (filteredUid == Process.INVALID_UID) {
+                                return;
+                            }
+                            break;
+                        default:
+                            pw.printf("Invalid argument at index %d: %s\n", i, arg);
+                            return;
+                    }
+                }
+            }
+        }
+        if (DEBUG_BROADCAST) {
+            Slogf.d(TAG_BROADCAST, "dumpBroadcastsLocked(): dumpPackage=%s, onlyHistory=%b, "
+                            + "onlyReceivers=%b, filteredUid=%d", dumpPackage, onlyHistory,
+                    onlyReceivers, filteredUid);
+        }
+
+        pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
+        if (!onlyHistory && dumpAll) {
+            if (mRegisteredReceivers.size() > 0) {
+                boolean printed = false;
+                Iterator it = mRegisteredReceivers.values().iterator();
+                while (it.hasNext()) {
+                    ReceiverList r = (ReceiverList) it.next();
+                    if (dumpPackage != null && (r.app == null
+                            || !dumpPackage.equals(r.app.info.packageName))) {
+                        continue;
+                    }
+                    if (filteredUid != Process.INVALID_UID && filteredUid != r.app.uid) {
+                        if (DEBUG_BROADCAST) {
+                            Slogf.v(TAG_BROADCAST, "dumpBroadcastsLocked(): skipping receiver whose"
+                                    + " uid (%d) is not %d: %s", r.app.uid, filteredUid, r.app);
+                        }
+                        continue;
+                    }
+                    if (!printed) {
+                        pw.println("  Registered Receivers:");
+                        needSep = true;
+                        printed = true;
+                        printedAnything = true;
+                    }
+                    pw.print("  * "); pw.println(r);
+                    r.dump(pw, "    ");
+                }
+            } else {
+                if (onlyReceivers) {
+                    pw.println("  (no registered receivers)");
+                }
+            }
+
+            if (!onlyReceivers) {
+                if (mReceiverResolver.dump(pw, needSep
+                                ? "\n  Receiver Resolver Table:" : "  Receiver Resolver Table:",
+                        "    ", dumpPackage, false, false)) {
+                    needSep = true;
+                    printedAnything = true;
+                }
+            }
+        }
+
+        if (!onlyReceivers) {
+            needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+                    dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+            printedAnything |= needSep;
+        }
+
+        needSep = true;
+
+        synchronized (mStickyBroadcasts) {
+            if (!onlyHistory && !onlyReceivers && mStickyBroadcasts != null
+                    && dumpPackage == null) {
+                for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    printedAnything = true;
+                    pw.print("  Sticky broadcasts for user ");
+                    pw.print(mStickyBroadcasts.keyAt(user));
+                    pw.println(":");
+                    StringBuilder sb = new StringBuilder(128);
+                    for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+                            : mStickyBroadcasts.valueAt(user).entrySet()) {
+                        pw.print("  * Sticky action ");
+                        pw.print(ent.getKey());
+                        if (dumpAll) {
+                            pw.println(":");
+                            ArrayList<StickyBroadcast> broadcasts = ent.getValue();
+                            final int N = broadcasts.size();
+                            for (int i = 0; i < N; i++) {
+                                final Intent intent = broadcasts.get(i).intent;
+                                final boolean deferUntilActive = broadcasts.get(i).deferUntilActive;
+                                sb.setLength(0);
+                                sb.append("    Intent: ");
+                                intent.toShortString(sb, false, true, false, false);
+                                pw.print(sb);
+                                if (deferUntilActive) {
+                                    pw.print(" [D]");
+                                }
+                                pw.println();
+                                pw.print("      originalCallingUid: ");
+                                pw.println(broadcasts.get(i).originalCallingUid);
+                                pw.println();
+                                Bundle bundle = intent.getExtras();
+                                if (bundle != null) {
+                                    pw.print("      extras: ");
+                                    pw.println(bundle);
+                                }
+                            }
+                        } else {
+                            pw.println("");
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!onlyHistory && !onlyReceivers && dumpAll) {
+            pw.println();
+            pw.println("  Queue " + mBroadcastQueue.toString() + ": "
+                    + mBroadcastQueue.describeStateLocked());
+            pw.println("  mHandler:");
+            mService.mHandler.dump(new PrintWriterPrinter(pw), "    ");
+            needSep = true;
+            printedAnything = true;
+        }
+
+        if (!printedAnything) {
+            pw.println("  (nothing)");
+        }
+    }
+
+    /**
+     * Gets an {@code int} argument from the given {@code index} on {@code args}, logging an error
+     * message on {@code pw} when it cannot be parsed.
+     *
+     * Returns {@code int} argument or {@code invalidValue} if it could not be parsed.
+     */
+    private static int getIntArg(PrintWriter pw, String[] args, int index, int invalidValue) {
+        if (index > args.length) {
+            pw.println("Missing argument");
+            return invalidValue;
+        }
+        String arg = args[index];
+        try {
+            return Integer.parseInt(arg);
+        } catch (Exception e) {
+            pw.printf("Non-numeric argument at index %d: %s\n", index, arg);
+            return invalidValue;
+        }
+    }
+
+    @NeverCompile
+    void dumpBroadcastStatsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        if (mCurBroadcastStats == null) {
+            return;
+        }
+
+        pw.println("ACTIVITY MANAGER BROADCAST STATS STATE (dumpsys activity broadcast-stats)");
+        final long now = SystemClock.elapsedRealtime();
+        if (mLastBroadcastStats != null) {
+            pw.print("  Last stats (from ");
+            TimeUtils.formatDuration(mLastBroadcastStats.mStartRealtime, now, pw);
+            pw.print(" to ");
+            TimeUtils.formatDuration(mLastBroadcastStats.mEndRealtime, now, pw);
+            pw.print(", ");
+            TimeUtils.formatDuration(mLastBroadcastStats.mEndUptime
+                    - mLastBroadcastStats.mStartUptime, pw);
+            pw.println(" uptime):");
+            if (!mLastBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
+                pw.println("    (nothing)");
+            }
+            pw.println();
+        }
+        pw.print("  Current stats (from ");
+        TimeUtils.formatDuration(mCurBroadcastStats.mStartRealtime, now, pw);
+        pw.print(" to now, ");
+        TimeUtils.formatDuration(SystemClock.uptimeMillis()
+                - mCurBroadcastStats.mStartUptime, pw);
+        pw.println(" uptime):");
+        if (!mCurBroadcastStats.dumpStats(pw, "    ", dumpPackage)) {
+            pw.println("    (nothing)");
+        }
+    }
+
+    @NeverCompile
+    void dumpBroadcastStatsCheckinLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean fullCheckin, String dumpPackage) {
+        if (mCurBroadcastStats == null) {
+            return;
+        }
+
+        if (mLastBroadcastStats != null) {
+            mLastBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+            if (fullCheckin) {
+                mLastBroadcastStats = null;
+                return;
+            }
+        }
+        mCurBroadcastStats.dumpCheckinStats(pw, dumpPackage);
+        if (fullCheckin) {
+            mCurBroadcastStats = null;
+        }
+    }
+
+    void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
+        if (mRegisteredReceivers.size() > 0) {
+            Iterator it = mRegisteredReceivers.values().iterator();
+            while (it.hasNext()) {
+                ReceiverList r = (ReceiverList) it.next();
+                r.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_LIST);
+            }
+        }
+        mReceiverResolver.dumpDebug(proto,
+                ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
+        mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
+        synchronized (mStickyBroadcasts) {
+            for (int user = 0; user < mStickyBroadcasts.size(); user++) {
+                long token = proto.start(
+                        ActivityManagerServiceDumpBroadcastsProto.STICKY_BROADCASTS);
+                proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
+                for (Map.Entry<String, ArrayList<StickyBroadcast>> ent
+                        : mStickyBroadcasts.valueAt(user).entrySet()) {
+                    long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
+                    proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
+                    for (StickyBroadcast broadcast : ent.getValue()) {
+                        broadcast.intent.dumpDebug(proto, StickyBroadcastProto.StickyAction.INTENTS,
+                                false, true, true, false);
+                    }
+                    proto.end(actionToken);
+                }
+                proto.end(token);
+            }
+        }
+
+        long handlerToken = proto.start(ActivityManagerServiceDumpBroadcastsProto.HANDLER);
+        proto.write(ActivityManagerServiceDumpBroadcastsProto.MainHandler.HANDLER,
+                mService.mHandler.toString());
+        mService.mHandler.getLooper().dumpDebug(proto,
+                ActivityManagerServiceDumpBroadcastsProto.MainHandler.LOOPER);
+        proto.end(handlerToken);
+    }
+
+    @VisibleForTesting
+    static final class StickyBroadcast {
+        public Intent intent;
+        public boolean deferUntilActive;
+        public int originalCallingUid;
+        /** The snapshot process state of the app who sent this broadcast */
+        public int originalCallingAppProcessState;
+        public String resolvedDataType;
+
+        public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
+                int originalCallingUid, int originalCallingAppProcessState,
+                String resolvedDataType) {
+            final StickyBroadcast b = new StickyBroadcast();
+            b.intent = intent;
+            b.deferUntilActive = deferUntilActive;
+            b.originalCallingUid = originalCallingUid;
+            b.originalCallingAppProcessState = originalCallingAppProcessState;
+            b.resolvedDataType = resolvedDataType;
+            return b;
+        }
+
+        @Override
+        public String toString() {
+            return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
+                    + originalCallingUid + ", originalCallingAppProcessState="
+                    + originalCallingAppProcessState + ", type=" + resolvedDataType + "}";
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a7b2eb1..8fe33d1 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -929,9 +929,9 @@
             // For ordered broadcast, check if the receivers for the new broadcast is a superset
             // of those for the previous one as skipping and removing only one of them could result
             // in an inconsistent state.
-            if (testRecord.ordered || testRecord.prioritized) {
+            if (testRecord.ordered) {
                 return containsAllReceivers(r, testRecord, recordsLookupCache);
-            } else if (testRecord.resultTo != null) {
+            } else if (testRecord.prioritized || testRecord.resultTo != null) {
                 return testRecord.getDeliveryState(testIndex) == DELIVERY_DEFERRED
                         ? r.containsReceiver(testRecord.receivers.get(testIndex))
                         : containsAllReceivers(r, testRecord, recordsLookupCache);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ab63e24..7e3f613 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1126,26 +1126,31 @@
         final int numLru = lruList.size();
         if (mConstants.USE_TIERED_CACHED_ADJ) {
             final long now = mInjector.getUptimeMillis();
+            int uiTargetAdj = 10;
             for (int i = numLru - 1; i >= 0; i--) {
                 ProcessRecord app = lruList.get(i);
                 final ProcessStateRecord state = app.mState;
                 final ProcessCachedOptimizerRecord opt = app.mOptRecord;
-                if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
-                        >= UNKNOWN_ADJ) {
+                if (!app.isKilledByAm() && app.getThread() != null
+                        && (state.getCurAdj() >= UNKNOWN_ADJ
+                            || (state.hasShownUi() && state.getCurAdj() >= CACHED_APP_MIN_ADJ))) {
                     final ProcessServiceRecord psr = app.mServices;
                     int targetAdj = CACHED_APP_MIN_ADJ;
 
                     if (opt != null && opt.isFreezeExempt()) {
                         // BIND_WAIVE_PRIORITY and the like get oom_adj 900
                         targetAdj += 0;
+                    } else if (state.hasShownUi() && uiTargetAdj < 15) {
+                        // The most recent 5 apps that have shown UI get 910-914
+                        targetAdj += uiTargetAdj++;
                     } else if ((state.getSetAdj() >= CACHED_APP_MIN_ADJ)
                             && (state.getLastStateTime()
                                     + mConstants.TIERED_CACHED_ADJ_DECAY_TIME) < now) {
                         // Older cached apps get 950
                         targetAdj += 50;
                     } else {
-                        // Newer cached apps get 910
-                        targetAdj += 10;
+                        // Newer cached apps get 920
+                        targetAdj += 20;
                     }
                     state.setCurRawAdj(targetAdj);
                     state.setCurAdj(psr.modifyRawOomAdj(targetAdj));
@@ -1201,6 +1206,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 +2186,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/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 5d48d09..2937307 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -33,6 +33,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.providers.settings.Flags;
 
 import android.aconfigd.Aconfigd.StorageRequestMessage;
 import android.aconfigd.Aconfigd.StorageRequestMessages;
@@ -51,6 +52,7 @@
 import java.util.Map;
 import java.util.List;
 import java.util.ArrayList;
+import java.util.Set;
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 
 /**
@@ -457,6 +459,24 @@
     }
 
     /**
+     * Send a request to aconfig storage to remove a flag local override.
+     *
+     * @param proto
+     * @param packageName the package of the flag
+     * @param flagName the name of the flag
+     */
+    static void writeFlagOverrideRemovalRequest(
+        ProtoOutputStream proto, String packageName, String flagName) {
+      long msgsToken = proto.start(StorageRequestMessages.MSGS);
+      long msgToken = proto.start(StorageRequestMessage.REMOVE_LOCAL_OVERRIDE_MESSAGE);
+      proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.PACKAGE_NAME, packageName);
+      proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.FLAG_NAME, flagName);
+      proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_ALL, false);
+      proto.end(msgToken);
+      proto.end(msgsToken);
+    }
+
+    /**
      * deserialize a flag input proto stream and log
      * @param proto
      */
@@ -501,8 +521,15 @@
         ProtoOutputStream requests = new ProtoOutputStream();
         for (String flagName : props.getKeyset()) {
             String flagValue = props.getString(flagName, null);
-            if (flagName == null || flagValue == null) {
-                continue;
+
+            if (Flags.syncLocalOverridesRemovalNewStorage()) {
+                if (flagName == null) {
+                    continue;
+                }
+            } else {
+                if (flagName == null || flagValue == null) {
+                    continue;
+                }
             }
 
             int idx = flagName.indexOf(":");
@@ -519,7 +546,13 @@
             }
             String packageName = fullFlagName.substring(0, idx);
             String realFlagName = fullFlagName.substring(idx+1);
-            writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
+
+            if (Flags.syncLocalOverridesRemovalNewStorage() && flagValue == null) {
+              writeFlagOverrideRemovalRequest(requests, packageName, realFlagName);
+            } else {
+              writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
+            }
+
             ++num_requests;
         }
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index bdba6bc..b186eaa 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1978,6 +1978,7 @@
                 boolean userSwitchUiEnabled;
                 synchronized (mLock) {
                     mCurrentUserId = userId;
+                    ActivityManager.invalidateGetCurrentUserIdCache();
                     userSwitchUiEnabled = mUserSwitchUiEnabled;
                 }
                 mInjector.updateUserConfiguration();
@@ -2239,6 +2240,7 @@
                 return true;
             }
             mTargetUserId = targetUserId;
+            ActivityManager.invalidateGetCurrentUserIdCache();
             userSwitchUiEnabled = mUserSwitchUiEnabled;
         }
         if (userSwitchUiEnabled) {
@@ -2316,6 +2318,7 @@
         synchronized (mLock) {
             nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL);
             mTargetUserId = UserHandle.USER_NULL;
+            ActivityManager.invalidateGetCurrentUserIdCache();
         }
         if (nextUserId != UserHandle.USER_NULL) {
             switchUser(nextUserId);
@@ -3021,6 +3024,9 @@
         mInjector.getUserManagerInternal().addUserLifecycleListener(mUserLifecycleListener);
         updateProfileRelatedCaches();
         mInjector.reportCurWakefulnessUsageEvent();
+
+        // IpcDataCache must be invalidated before it starts caching.
+        ActivityManager.invalidateGetCurrentUserIdCache();
     }
 
     // TODO(b/266158156): remove this method if initial system user boot logic is refactored?
@@ -3184,6 +3190,9 @@
 
     @GuardedBy("mLock")
     private int getCurrentOrTargetUserIdLU() {
+        // Note: this result is currently cached by ActivityManager.getCurrentUser() - changes to
+        // the logic here may require updating how the cache is invalidated.
+        // See ActivityManager.invalidateGetCurrentUserIdCache() for more details.
         return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
     }
 
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/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 154b52b..6ae6f3d 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -33,7 +33,6 @@
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
@@ -3115,11 +3114,6 @@
                     packageName);
         }
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
-            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-            if (code == OP_BLUETOOTH_CONNECT) {
-                Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as incoming "
-                        + "package: " + packageName + " and uid: " + uid + " is invalid");
-            }
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -3149,13 +3143,6 @@
             }
         } catch (SecurityException e) {
             logVerifyAndGetBypassFailure(uid, e, "noteOperation");
-            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-            if (code == OP_BLUETOOTH_CONNECT) {
-                Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
-                        + " verifyAndGetBypass returned a SecurityException for package: "
-                        + packageName + " and uid: " + uid + " and attributionTag: "
-                        + attributionTag, e);
-            }
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -3173,17 +3160,6 @@
                 if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName + "flags: " +
                         AppOpsManager.flagsToString(flags));
-                // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                if (code == OP_BLUETOOTH_CONNECT) {
-                    Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
-                            + " #getOpsLocked returned null for"
-                            + " uid: " + uid
-                            + " packageName: " + packageName
-                            + " attributionTag: " + attributionTag
-                            + " pvr.isAttributionTagValid: " + pvr.isAttributionTagValid
-                            + " pvr.bypass: " + pvr.bypass);
-                    Slog.e(TAG, "mUidStates.get(" + uid + "): " + mUidStates.get(uid));
-                }
                 return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                         packageName);
             }
@@ -3228,11 +3204,6 @@
                     attributedOp.rejected(uidState.getState(), flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
                             virtualDeviceId, flags, uidMode);
-                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                    if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
-                        Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
-                                + " uid mode is MODE_ERRORED");
-                    }
                     return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
                 }
             } else {
@@ -3252,11 +3223,6 @@
                     attributedOp.rejected(uidState.getState(), flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
                             virtualDeviceId, flags, mode);
-                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                    if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
-                        Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
-                                + " package mode is MODE_ERRORED");
-                    }
                     return new SyncNotedAppOp(mode, code, attributionTag, packageName);
                 }
             }
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 8d8a54e..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
@@ -1457,7 +1462,7 @@
     private int setDevicesRoleForCapturePreset(int capturePreset, int role,
                                                @NonNull List<AudioDeviceAttributes> devices) {
         return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
-            return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
+            return mAudioSystem.setDevicesRoleForCapturePreset(p, r, d);
         }, (p, r, d) -> {
                 return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
             }, capturePreset, role, devices);
@@ -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..4e24cf3 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,8 @@
     private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
 
     private final Executor mAudioServerLifecycleExecutor;
+    private long mSysPropListenerNativeHandle;
+    private final List<Future> mScheduledPermissionTasks = new ArrayList();
 
     private IMediaProjectionManager mProjectionService; // to validate projection token
 
@@ -1092,7 +1099,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 +1230,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 +1697,10 @@
 
     public void onSystemReady() {
         mSystemReady = true;
+        if (audioserverPermissions()) {
+            setupPermissionListener();
+        }
         scheduleLoadSoundEffects();
-
         mDeviceBroker.onSystemReady();
 
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
@@ -10608,6 +10590,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));
+                }
+            };
+            mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange(
+                    PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    task);
+        } else {
+            mAudioSystem.listenForSystemPropertyChange(
+                    PermissionManager.CACHE_KEY_PACKAGE_INFO,
+                    () -> mAudioServerLifecycleExecutor.execute(
+                                mPermissionProvider::onPermissionStateChanged));
+        }
+    }
 
     //==========================================================================================
     // Audio Focus
@@ -10691,7 +10734,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 +10743,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 +10803,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 +10821,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 +10839,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 +14711,25 @@
         return activeAssistantUids;
     }
 
+    @Override
+    /** @see AudioManager#permissionUpdateBarrier() */
+    public void permissionUpdateBarrier() {
+        mAudioSystem.triggerSystemPropertyUpdate(mSysPropListenerNativeHandle);
+        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/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index d083c68..5cabdde 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -748,8 +748,12 @@
         return AudioSystem.setMasterMute(mute);
     }
 
-    public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
-        AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
+    public long listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
+        return AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
+    }
+
+    public void triggerSystemPropertyUpdate(long handle) {
+        AudioSystem.triggerSystemPropertyUpdate(handle);
     }
 
     /**
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/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 70f3193..7e26356 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -1302,7 +1302,7 @@
         mEventLogger.enqueue((new EventLogger.StringEvent(
                 "abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
                     + "/" + Binder.getCallingPid()
-                    + " clientId=" + clientId))
+                    + " clientId=" + clientId + " callingPack=" + callingPackageName))
                 .printLog(TAG));
         try {
             // this will take care of notifying the new focus owner if needed
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..ac3c028 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -120,7 +120,9 @@
                         userId), trustManager)) {
             isMandatoryBiometricsAuthentication = true;
             promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
-            promptInfo.setNegativeButtonText(context.getString(R.string.cancel));
+            if (promptInfo.getNegativeButtonText() == null) {
+                promptInfo.setNegativeButtonText(context.getString(R.string.cancel));
+            }
         }
 
         final boolean biometricRequested = Utils.isBiometricRequested(promptInfo);
@@ -447,6 +449,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..bc58501 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -98,7 +98,8 @@
                 foldType(operationContext.getFoldState()),
                 operationContext.getOrderAndIncrement(),
                 toProtoWakeReason(operationContext),
-                toProtoWakeReasonDetails(operationContext));
+                toProtoWakeReasonDetails(operationContext),
+                operationContext.getIsMandatoryBiometrics());
     }
 
     /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */
@@ -149,7 +150,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/crashrecovery/CrashRecoveryUtils.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
new file mode 100644
index 0000000..3eb3380
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.crashrecovery;
+
+import android.os.Environment;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+
+/**
+ * Class containing helper methods for the CrashRecoveryModule.
+ *
+ * @hide
+ */
+public class CrashRecoveryUtils {
+    private static final String TAG = "CrashRecoveryUtils";
+    private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 1000 * 1000; // ~1MB
+    private static final Object sFileLock = new Object();
+
+    /** Persist recovery related events in crashrecovery events file.**/
+    public static void logCrashRecoveryEvent(int priority, String msg) {
+        Slog.println(priority, TAG, msg);
+        try {
+            File fname = getCrashRecoveryEventsFile();
+            synchronized (sFileLock) {
+                FileOutputStream out = new FileOutputStream(fname, true);
+                PrintWriter pw = new PrintWriter(out);
+                String dateString = LocalDateTime.now(ZoneId.systemDefault()).toString();
+                pw.println(dateString + ": " + msg);
+                pw.close();
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage());
+        }
+    }
+
+    /** Dump recovery related events from crashrecovery events file.**/
+    public static void dumpCrashRecoveryEvents(IndentingPrintWriter pw) {
+        pw.println("CrashRecovery Events: ");
+        pw.increaseIndent();
+        final File file = getCrashRecoveryEventsFile();
+        final long skipSize = file.length() - MAX_CRITICAL_INFO_DUMP_SIZE;
+        synchronized (sFileLock) {
+            try (BufferedReader in = new BufferedReader(new FileReader(file))) {
+                if (skipSize > 0) {
+                    in.skip(skipSize);
+                }
+                String line;
+                while ((line = in.readLine()) != null) {
+                    pw.println(line);
+                }
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage());
+            }
+        }
+        pw.decreaseIndent();
+    }
+
+    private static File getCrashRecoveryEventsFile() {
+        File systemDir = new File(Environment.getDataDirectory(), "system");
+        return new File(systemDir, "crashrecovery-events.txt");
+    }
+}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 240e91b..907e7c6 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -45,6 +45,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.util.EventLog;
+import android.util.IndentingPrintWriter;
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -562,6 +563,7 @@
     public void resetShortTermModel() {
         mCurrentBrightnessMapper.clearUserDataPoints();
         mShortTermModel.reset();
+        Slog.i(TAG, "Resetting short term model");
     }
 
     public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
@@ -598,74 +600,79 @@
     }
 
     public void dump(PrintWriter pw) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+        ipw.increaseIndent();
         pw.println();
         pw.println("Automatic Brightness Controller Configuration:");
-        pw.println("  mState=" + configStateToString(mState));
-        pw.println("  mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
-        pw.println("  mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
-        pw.println("  mDozeScaleFactor=" + mDozeScaleFactor);
-        pw.println("  mInitialLightSensorRate=" + mInitialLightSensorRate);
-        pw.println("  mNormalLightSensorRate=" + mNormalLightSensorRate);
-        pw.println("  mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
-        pw.println("  mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
-        pw.println("  mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
-        pw.println("  mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle);
-        pw.println("  mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle);
-        pw.println("  mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
-        pw.println("  mAmbientLightHorizonLong=" + mAmbientLightHorizonLong);
-        pw.println("  mAmbientLightHorizonShort=" + mAmbientLightHorizonShort);
-        pw.println("  mWeightingIntercept=" + mWeightingIntercept);
+        pw.println("----------------------------------------------");
+        ipw.println("mState=" + configStateToString(mState));
+        ipw.println("mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
+        ipw.println("mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
+        ipw.println("mDozeScaleFactor=" + mDozeScaleFactor);
+        ipw.println("mInitialLightSensorRate=" + mInitialLightSensorRate);
+        ipw.println("mNormalLightSensorRate=" + mNormalLightSensorRate);
+        ipw.println("mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
+        ipw.println("mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig);
+        ipw.println("mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig);
+        ipw.println("mBrighteningLightDebounceConfigIdle=" + mBrighteningLightDebounceConfigIdle);
+        ipw.println("mDarkeningLightDebounceConfigIdle=" + mDarkeningLightDebounceConfigIdle);
+        ipw.println("mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig);
+        ipw.println("mAmbientLightHorizonLong=" + mAmbientLightHorizonLong);
+        ipw.println("mAmbientLightHorizonShort=" + mAmbientLightHorizonShort);
+        ipw.println("mWeightingIntercept=" + mWeightingIntercept);
 
         pw.println();
         pw.println("Automatic Brightness Controller State:");
-        pw.println("  mLightSensor=" + mLightSensor);
-        pw.println("  mLightSensorEnabled=" + mLightSensorEnabled);
-        pw.println("  mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
-        pw.println("  mCurrentLightSensorRate=" + mCurrentLightSensorRate);
-        pw.println("  mAmbientLux=" + mAmbientLux);
-        pw.println("  mAmbientLuxValid=" + mAmbientLuxValid);
-        pw.println("  mPreThresholdLux=" + mPreThresholdLux);
-        pw.println("  mPreThresholdBrightness=" + mPreThresholdBrightness);
-        pw.println("  mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold);
-        pw.println("  mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold);
-        pw.println("  mScreenBrighteningThreshold=" + mScreenBrighteningThreshold);
-        pw.println("  mScreenDarkeningThreshold=" + mScreenDarkeningThreshold);
-        pw.println("  mLastObservedLux=" + mLastObservedLux);
-        pw.println("  mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
-        pw.println("  mRecentLightSamples=" + mRecentLightSamples);
-        pw.println("  mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
-        pw.println("  mScreenAutoBrightness=" + mScreenAutoBrightness);
-        pw.println("  mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy));
-        pw.println("  mShortTermModel=");
-        mShortTermModel.dump(pw);
-        pw.println("  mPausedShortTermModel=");
-        mPausedShortTermModel.dump(pw);
+        pw.println("--------------------------------------");
+        ipw.println("mLightSensor=" + mLightSensor);
+        ipw.println("mLightSensorEnabled=" + mLightSensorEnabled);
+        ipw.println("mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
+        ipw.println("mCurrentLightSensorRate=" + mCurrentLightSensorRate);
+        ipw.println("mAmbientLux=" + mAmbientLux);
+        ipw.println("mAmbientLuxValid=" + mAmbientLuxValid);
+        ipw.println("mPreThresholdLux=" + mPreThresholdLux);
+        ipw.println("mPreThresholdBrightness=" + mPreThresholdBrightness);
+        ipw.println("mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold);
+        ipw.println("mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold);
+        ipw.println("mScreenBrighteningThreshold=" + mScreenBrighteningThreshold);
+        ipw.println("mScreenDarkeningThreshold=" + mScreenDarkeningThreshold);
+        ipw.println("mLastObservedLux=" + mLastObservedLux);
+        ipw.println("mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
+        ipw.println("mRecentLightSamples=" + mRecentLightSamples);
+        ipw.println("mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
+        ipw.println("mScreenAutoBrightness=" + mScreenAutoBrightness);
+        ipw.println("mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy));
+        ipw.println("mShortTermModel=");
 
-        pw.println();
-        pw.println("  mBrightnessAdjustmentSamplePending=" + mBrightnessAdjustmentSamplePending);
-        pw.println("  mBrightnessAdjustmentSampleOldLux=" + mBrightnessAdjustmentSampleOldLux);
-        pw.println("  mBrightnessAdjustmentSampleOldBrightness="
+        mShortTermModel.dump(ipw);
+        ipw.println("mPausedShortTermModel=");
+        mPausedShortTermModel.dump(ipw);
+
+        ipw.println();
+        ipw.println("mBrightnessAdjustmentSamplePending=" + mBrightnessAdjustmentSamplePending);
+        ipw.println("mBrightnessAdjustmentSampleOldLux=" + mBrightnessAdjustmentSampleOldLux);
+        ipw.println("mBrightnessAdjustmentSampleOldBrightness="
                 + mBrightnessAdjustmentSampleOldBrightness);
-        pw.println("  mForegroundAppPackageName=" + mForegroundAppPackageName);
-        pw.println("  mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
-        pw.println("  mForegroundAppCategory=" + mForegroundAppCategory);
-        pw.println("  mPendingForegroundAppCategory=" + mPendingForegroundAppCategory);
-        pw.println("  Current mode="
+        ipw.println("mForegroundAppPackageName=" + mForegroundAppPackageName);
+        ipw.println("mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
+        ipw.println("mForegroundAppCategory=" + mForegroundAppCategory);
+        ipw.println("mPendingForegroundAppCategory=" + mPendingForegroundAppCategory);
+        ipw.println("Current mode="
                 + autoBrightnessModeToString(mCurrentBrightnessMapper.getMode()));
 
         for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) {
-            pw.println();
-            pw.println("  Mapper for mode "
+            ipw.println();
+            ipw.println("Mapper for mode "
                     + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + ":");
-            mBrightnessMappingStrategyMap.valueAt(i).dump(pw,
+            mBrightnessMappingStrategyMap.valueAt(i).dump(ipw,
                     mBrightnessRangeController.getNormalBrightnessMax());
         }
 
-        pw.println();
-        pw.println("  mAmbientBrightnessThresholds=" + mAmbientBrightnessThresholds);
-        pw.println("  mAmbientBrightnessThresholdsIdle=" + mAmbientBrightnessThresholdsIdle);
-        pw.println("  mScreenBrightnessThresholds=" + mScreenBrightnessThresholds);
-        pw.println("  mScreenBrightnessThresholdsIdle=" + mScreenBrightnessThresholdsIdle);
+        ipw.println();
+        ipw.println("mAmbientBrightnessThresholds=" + mAmbientBrightnessThresholds);
+        ipw.println("mAmbientBrightnessThresholdsIdle=" + mAmbientBrightnessThresholdsIdle);
+        ipw.println("mScreenBrightnessThresholds=" + mScreenBrightnessThresholds);
+        ipw.println("mScreenBrightnessThresholdsIdle=" + mScreenBrightnessThresholdsIdle);
     }
 
     public float[] getLastSensorValues() {
@@ -1339,8 +1346,10 @@
                     + "\n mIsValid: " + mIsValid;
         }
 
-        void dump(PrintWriter pw) {
-            pw.println(this);
+        void dump(IndentingPrintWriter ipw) {
+            ipw.increaseIndent();
+            ipw.println(this);
+            ipw.decreaseIndent();
         }
 
     }
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index b0507fb..6a019f3 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -1073,7 +1073,9 @@
             pw.println("  mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied);
             pw.println("  shortTermModelTimeout=" + getShortTermModelTimeout());
 
-            pw.println("  Previous short-term models (oldest to newest): ");
+            if (!mPreviousBrightnessSplines.isEmpty()) {
+                pw.println("  Previous short-term models (oldest to newest): ");
+            }
             for (int i = 0; i < mPreviousBrightnessSplines.size(); i++) {
                 pw.println("  Computed at "
                         + FORMAT.format(new Date(mBrightnessSplineChangeTimes.get(i))) + ": ");
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 7d26004..5488446 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -150,6 +150,13 @@
     }
 
     /**
+     * Flush the brightness update that has been made to the persistent data store.
+     */
+    public void saveIfNeeded() {
+        mPersistentDataStore.saveIfNeeded();
+    }
+
+    /**
      * @return The brightness for the default display in nits. Used when the underlying display
      * device has changed but we want to persist the nit value.
      */
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index 631e751..b56a234 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -265,6 +265,7 @@
 
     private void dumpLocal(PrintWriter pw) {
         pw.println("BrightnessThrottler:");
+        pw.println("--------------------");
         pw.println("  mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId);
         pw.println("  mThermalThrottlingData=" + mThermalThrottlingData);
         pw.println("  mUniqueDisplayId=" + mUniqueDisplayId);
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index ac5dd20..0f65360 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -782,6 +782,7 @@
 
     public void dump(final PrintWriter pw) {
         pw.println("BrightnessTracker state:");
+        pw.println("------------------------");
         synchronized (mDataCollectionLock) {
             pw.println("  mStarted=" + mStarted);
             pw.println("  mLightSensor=" + mLightSensor);
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 146810f..4c133ff 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -95,6 +95,7 @@
 
     public void dumpLocked(IndentingPrintWriter ipw) {
         ipw.println("DeviceStateToLayoutMap:");
+        ipw.println("-----------------------");
         ipw.increaseIndent();
 
         ipw.println("mIsPortInDisplayLayoutEnabled=" + mIsPortInDisplayLayoutEnabled);
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index dc611fc..334dda0 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.hardware.display.BrightnessInfo;
 import android.text.TextUtils;
 
 import com.android.server.display.brightness.BrightnessEvent;
@@ -50,6 +51,8 @@
 
     private final boolean mIsUserInitiatedChange;
 
+    private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason;
+
     private DisplayBrightnessState(Builder builder) {
         mBrightness = builder.getBrightness();
         mHdrBrightness = builder.getHdrBrightness();
@@ -64,6 +67,7 @@
         mBrightnessEvent = builder.getBrightnessEvent();
         mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag();
         mIsUserInitiatedChange = builder.isUserInitiatedChange();
+        mBrightnessMaxReason = builder.getBrightnessMaxReason();
     }
 
     /**
@@ -159,6 +163,13 @@
         return mIsUserInitiatedChange;
     }
 
+    /**
+     * Gets reason for max brightness restriction
+     */
+    public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() {
+        return mBrightnessMaxReason;
+    }
+
     @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -180,6 +191,8 @@
                 .append(Objects.toString(mBrightnessEvent, "null"));
         stringBuilder.append("\n    mBrightnessAdjustmentFlag:").append(mBrightnessAdjustmentFlag);
         stringBuilder.append("\n    mIsUserInitiatedChange:").append(mIsUserInitiatedChange);
+        stringBuilder.append("\n    mBrightnessMaxReason:")
+                .append(BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
         return stringBuilder.toString();
     }
 
@@ -212,7 +225,8 @@
                     == otherState.shouldUpdateScreenBrightnessSetting()
                 && Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent())
                 && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag()
-                && mIsUserInitiatedChange == otherState.isUserInitiatedChange();
+                && mIsUserInitiatedChange == otherState.isUserInitiatedChange()
+                && mBrightnessMaxReason == otherState.getBrightnessMaxReason();
     }
 
     @Override
@@ -221,7 +235,7 @@
                 mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
                 mCustomAnimationRate,
                 mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag,
-                mIsUserInitiatedChange);
+                mIsUserInitiatedChange, mBrightnessMaxReason);
     }
 
     /**
@@ -245,12 +259,11 @@
         private float mMinBrightness;
         private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
         private boolean mShouldUpdateScreenBrightnessSetting;
-
         private BrightnessEvent mBrightnessEvent;
-
-        public int mBrightnessAdjustmentFlag = 0;
-
+        private int mBrightnessAdjustmentFlag = 0;
         private boolean mIsUserInitiatedChange;
+        private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
 
         /**
          * Create a builder starting with the values from the specified {@link
@@ -274,6 +287,7 @@
             builder.setBrightnessEvent(state.getBrightnessEvent());
             builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag());
             builder.setIsUserInitiatedChange(state.isUserInitiatedChange());
+            builder.setBrightnessMaxReason(state.getBrightnessMaxReason());
             return builder;
         }
 
@@ -506,5 +520,21 @@
             mIsUserInitiatedChange = isUserInitiatedChange;
             return this;
         }
+
+        /**
+         * Gets reason for max brightness restriction
+         */
+        public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() {
+            return mBrightnessMaxReason;
+        }
+
+        /**
+         * Sets reason for max brightness restriction
+         */
+        public Builder setBrightnessMaxReason(
+                @BrightnessInfo.BrightnessMaxReason int brightnessMaxReason) {
+            mBrightnessMaxReason = brightnessMaxReason;
+            return this;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index cc115f1..d78fdfa 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -150,7 +150,9 @@
  *      <screenBrightnessDefault>0.65</screenBrightnessDefault>
  *      <powerThrottlingConfig>
  *        <brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed>
- *        <pollingWindowMillis>15</pollingWindowMillis>
+ *        <customAnimationRateSec>0.004</customAnimationRateSec>
+ *        <pollingWindowMaxMillis>30000</pollingWindowMaxMillis>
+ *        <pollingWindowMinMillis>10000</pollingWindowMinMillis>
  *          <powerThrottlingMap>
  *              <powerThrottlingPoint>
  *                  <thermalStatus>severe</thermalStatus>
@@ -2184,9 +2186,13 @@
             return;
         }
         float lowestBrightnessCap = powerThrottlingCfg.getBrightnessLowestCapAllowed().floatValue();
-        int pollingWindowMillis = powerThrottlingCfg.getPollingWindowMillis().intValue();
+        float customAnimationRateSec = powerThrottlingCfg.getCustomAnimationRateSec().floatValue();
+        int pollingWindowMaxMillis = powerThrottlingCfg.getPollingWindowMaxMillis().intValue();
+        int pollingWindowMinMillis = powerThrottlingCfg.getPollingWindowMinMillis().intValue();
         mPowerThrottlingConfigData = new PowerThrottlingConfigData(lowestBrightnessCap,
-                                                                   pollingWindowMillis);
+                                                                   customAnimationRateSec,
+                                                                   pollingWindowMaxMillis,
+                                                                   pollingWindowMinMillis);
     }
 
     private void loadRefreshRateSetting(DisplayConfiguration config) {
@@ -2980,12 +2986,19 @@
     public static class PowerThrottlingConfigData {
         /** Lowest brightness cap allowed for this device. */
         public final float brightnessLowestCapAllowed;
-        /** Time window for polling power in seconds. */
-        public final int pollingWindowMillis;
+        /** Time take to animate brightness in seconds. */
+        public final float customAnimationRateSec;
+        /** Time window for maximum polling power in milliseconds. */
+        public final int pollingWindowMaxMillis;
+        /** Time window for minimum polling power in milliseconds. */
+        public final int pollingWindowMinMillis;
         public PowerThrottlingConfigData(float brightnessLowestCapAllowed,
-                int pollingWindowMillis) {
+                float customAnimationRateSec, int pollingWindowMaxMillis,
+                int pollingWindowMinMillis) {
             this.brightnessLowestCapAllowed = brightnessLowestCapAllowed;
-            this.pollingWindowMillis = pollingWindowMillis;
+            this.customAnimationRateSec = customAnimationRateSec;
+            this.pollingWindowMaxMillis = pollingWindowMaxMillis;
+            this.pollingWindowMinMillis = pollingWindowMinMillis;
         }
 
         @Override
@@ -2993,7 +3006,9 @@
             return "PowerThrottlingConfigData{"
                     + "brightnessLowestCapAllowed: "
                     + brightnessLowestCapAllowed
-                    + ", pollingWindowMillis: " + pollingWindowMillis
+                    + ", customAnimationRateSec: " + customAnimationRateSec
+                    + ", pollingWindowMaxMillis: " + pollingWindowMaxMillis
+                    + ", pollingWindowMinMillis: " + pollingWindowMinMillis
                     + "} ";
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 55a6ce7..2b732ea 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -269,6 +269,7 @@
 
     private final Context mContext;
     private final DisplayManagerHandler mHandler;
+    private final HandlerExecutor mHandlerExecutor;
     private final Handler mUiHandler;
     private final DisplayModeDirector mDisplayModeDirector;
     private final ExternalDisplayPolicy mExternalDisplayPolicy;
@@ -315,6 +316,7 @@
     public boolean mSafeMode;
 
     // All callback records indexed by calling process id.
+    @GuardedBy("mSyncRoot")
     private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
 
     /**
@@ -354,7 +356,7 @@
             new CopyOnWriteArrayList<>();
 
     /** All {@link DisplayPowerController}s indexed by {@link LogicalDisplay} ID. */
-    private final SparseArray<DisplayPowerControllerInterface> mDisplayPowerControllers =
+    private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
             new SparseArray<>();
 
     /** {@link DisplayBlanker} used by all {@link DisplayPowerController}s. */
@@ -602,6 +604,7 @@
         mContext = context;
         mFlags = injector.getFlags();
         mHandler = new DisplayManagerHandler(displayThreadLooper);
+        mHandlerExecutor = new HandlerExecutor(mHandler);
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
@@ -726,7 +729,7 @@
                 if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
                     return;
                 }
-                final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(
+                final DisplayPowerController dpc = mDisplayPowerControllers.get(
                         logicalDisplay.getDisplayIdLocked());
                 if (dpc == null) {
                     return;
@@ -760,12 +763,13 @@
             mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
             mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
             mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+
             ActivityManager activityManager = mContext.getSystemService(ActivityManager.class);
             activityManager.addOnUidImportanceListener(mUidImportanceListener, IMPORTANCE_CACHED);
 
             mDeviceStateManager = LocalServices.getService(DeviceStateManagerInternal.class);
             mContext.getSystemService(DeviceStateManager.class).registerCallback(
-                    new HandlerExecutor(mHandler), new DeviceStateListener());
+                    mHandlerExecutor, new DeviceStateListener());
 
             mLogicalDisplayMapper.onWindowManagerReady();
             scheduleTraversalLocked(false);
@@ -1019,6 +1023,10 @@
     private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
         @Override
         public void onUidImportance(int uid, int importance) {
+          onUidImportanceInternal(uid, importance);
+        }
+
+        private void onUidImportanceInternal(int uid, int importance) {
             synchronized (mPendingCallbackSelfLocked) {
                 if (importance >= IMPORTANCE_GONE) {
                     // Clean up as the app is already gone
@@ -1181,7 +1189,10 @@
 
     private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
             frameRateOverrides, DisplayInfo info, int callingUid) {
+        // Start with the display frame rate
         float frameRateHz = info.renderFrameRate;
+
+        // If the app has a specific override, use that instead
         for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
             if (frameRateOverride.uid == callingUid) {
                 frameRateHz = frameRateOverride.frameRateHz;
@@ -1200,18 +1211,21 @@
                                 DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, callingUid);
 
         // Override the refresh rate only if it is a divisor of the current
-        // refresh rate. This calculation needs to be in sync with the native code
+        // vsync rate. This calculation needs to be in sync with the native code
         // in RefreshRateSelector::getFrameRateDivisor
         Display.Mode currentMode = info.getMode();
-        float numPeriods = currentMode.getRefreshRate() / frameRateHz;
+        float vsyncRate = currentMode.getVsyncRate();
+        float numPeriods = vsyncRate / frameRateHz;
         float numPeriodsRound = Math.round(numPeriods);
         if (Math.abs(numPeriods - numPeriodsRound) > THRESHOLD_FOR_REFRESH_RATES_DIVISORS) {
             return info;
         }
-        frameRateHz = currentMode.getRefreshRate() / numPeriodsRound;
+        frameRateHz = vsyncRate / numPeriodsRound;
 
         DisplayInfo overriddenInfo = new DisplayInfo();
         overriddenInfo.copyFrom(info);
+
+        // If there is a mode that matches the override, use that one
         for (Display.Mode mode : info.supportedModes) {
             if (!mode.equalsExceptRefreshRate(currentMode)) {
                 continue;
@@ -1231,8 +1245,9 @@
                 return overriddenInfo;
             }
         }
-
         overriddenInfo.refreshRateOverride = frameRateHz;
+
+        // Create a fake mode for app compat
         if (!displayModeReturnsPhysicalRefreshRate) {
             overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
                     info.supportedModes.length + 1);
@@ -2051,7 +2066,7 @@
             configurePreferredDisplayModeLocked(display);
         }
 
-        DisplayPowerControllerInterface dpc = addDisplayPowerControllerLocked(display);
+        DisplayPowerController dpc = addDisplayPowerControllerLocked(display);
         if (dpc != null) {
             final int leadDisplayId = display.getLeadDisplayIdLocked();
             updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
@@ -2060,7 +2075,7 @@
             // that the follower display was added before the lead display.
             mLogicalDisplayMapper.forEachLocked(d -> {
                 if (d.getLeadDisplayIdLocked() == displayId) {
-                    DisplayPowerControllerInterface followerDpc =
+                    DisplayPowerController followerDpc =
                             mDisplayPowerControllers.get(d.getDisplayIdLocked());
                     if (followerDpc != null) {
                         updateDisplayPowerControllerLeaderLocked(followerDpc, displayId);
@@ -2144,7 +2159,7 @@
         scheduleTraversalLocked(false);
         mPersistentDataStore.saveIfNeeded();
 
-        DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+        DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
         if (dpc != null) {
             final int leadDisplayId = display.getLeadDisplayIdLocked();
             updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
@@ -2158,7 +2173,7 @@
     }
 
     private void updateDisplayPowerControllerLeaderLocked(
-            @NonNull DisplayPowerControllerInterface dpc, int leadDisplayId) {
+            @NonNull DisplayPowerController dpc, int leadDisplayId) {
         if (dpc.getLeadDisplayId() == leadDisplayId) {
             // Lead display hasn't changed, nothing to do.
             return;
@@ -2167,7 +2182,7 @@
         // If it has changed, then we need to unregister from the previous leader if there was one.
         final int prevLeaderId = dpc.getLeadDisplayId();
         if (prevLeaderId != Layout.NO_LEAD_DISPLAY) {
-            final DisplayPowerControllerInterface prevLeader =
+            final DisplayPowerController prevLeader =
                     mDisplayPowerControllers.get(prevLeaderId);
             if (prevLeader != null) {
                 prevLeader.removeDisplayBrightnessFollower(dpc);
@@ -2176,7 +2191,7 @@
 
         // And then, if it's following, register it with the new one.
         if (leadDisplayId != Layout.NO_LEAD_DISPLAY) {
-            final DisplayPowerControllerInterface newLeader =
+            final DisplayPowerController newLeader =
                     mDisplayPowerControllers.get(leadDisplayId);
             if (newLeader != null) {
                 newLeader.addDisplayBrightnessFollower(dpc);
@@ -2217,7 +2232,7 @@
     private void releaseDisplayAndEmitEvent(LogicalDisplay display, int event) {
         final int displayId = display.getDisplayIdLocked();
 
-        final DisplayPowerControllerInterface dpc =
+        final DisplayPowerController dpc =
                 mDisplayPowerControllers.removeReturnOld(displayId);
         if (dpc != null) {
             updateDisplayPowerControllerLeaderLocked(dpc, Layout.NO_LEAD_DISPLAY);
@@ -2264,7 +2279,7 @@
 
     private void handleLogicalDisplayDeviceStateTransitionLocked(@NonNull LogicalDisplay display) {
         final int displayId = display.getDisplayIdLocked();
-        final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+        final DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
         if (dpc != null) {
             final int leadDisplayId = display.getLeadDisplayIdLocked();
             updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
@@ -2685,14 +2700,14 @@
             if (userId != mCurrentUserId) {
                 return;
             }
-            DisplayPowerControllerInterface dpc = getDpcFromUniqueIdLocked(uniqueId);
+            DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId);
             if (dpc != null) {
                 dpc.setBrightnessConfiguration(c, /* shouldResetShortTermModel= */ true);
             }
         }
     }
 
-    private DisplayPowerControllerInterface getDpcFromUniqueIdLocked(String uniqueId) {
+    private DisplayPowerController getDpcFromUniqueIdLocked(String uniqueId) {
         final DisplayDevice displayDevice = mDisplayDeviceRepo.getByUniqueIdLocked(uniqueId);
         final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayDevice);
         if (logicalDisplay != null) {
@@ -2733,7 +2748,7 @@
                 final BrightnessConfiguration config =
                         getBrightnessConfigForDisplayWithPdsFallbackLocked(uniqueId, userSerial);
                 if (config != null) {
-                    final DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(
+                    final DisplayPowerController dpc = mDisplayPowerControllers.get(
                             logicalDisplay.getDisplayIdLocked());
                     if (dpc != null) {
                         dpc.setBrightnessConfiguration(config,
@@ -2980,7 +2995,7 @@
 
     void setAutoBrightnessLoggingEnabled(boolean enabled) {
         synchronized (mSyncRoot) {
-            final DisplayPowerControllerInterface displayPowerController =
+            final DisplayPowerController displayPowerController =
                     mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY);
             if (displayPowerController != null) {
                 displayPowerController.setAutoBrightnessLoggingEnabled(enabled);
@@ -2990,7 +3005,7 @@
 
     void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
         synchronized (mSyncRoot) {
-            final DisplayPowerControllerInterface displayPowerController =
+            final DisplayPowerController displayPowerController =
                     mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY);
             if (displayPowerController != null) {
                 displayPowerController.setDisplayWhiteBalanceLoggingEnabled(enabled);
@@ -3016,7 +3031,7 @@
 
     void setAmbientColorTemperatureOverride(float cct) {
         synchronized (mSyncRoot) {
-            final DisplayPowerControllerInterface displayPowerController =
+            final DisplayPowerController displayPowerController =
                     mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY);
             if (displayPowerController != null) {
                 displayPowerController.setAmbientColorTemperatureOverride(cct);
@@ -3026,7 +3041,7 @@
 
     void setDockedAndIdleEnabled(boolean enabled, int displayId) {
         synchronized (mSyncRoot) {
-            final DisplayPowerControllerInterface displayPowerController =
+            final DisplayPowerController displayPowerController =
                     mDisplayPowerControllers.get(displayId);
             if (displayPowerController != null) {
                 displayPowerController.setAutomaticScreenBrightnessMode(enabled
@@ -3225,36 +3240,40 @@
         // After releasing the lock, send the notifications out.
         for (int i = 0; i < mTempCallbacks.size(); i++) {
             CallbackRecord callbackRecord = mTempCallbacks.get(i);
-            final int uid = callbackRecord.mUid;
-            final int pid = callbackRecord.mPid;
-            if (isUidCached(uid)) {
-                // For cached apps, save the pending event until it becomes non-cached
-                synchronized (mPendingCallbackSelfLocked) {
-                    SparseArray<PendingCallback> pendingCallbacks = mPendingCallbackSelfLocked.get(
-                            uid);
-                    if (extraLogging(callbackRecord.mPackageName)) {
-                        Slog.i(TAG, "Uid is cached: " + uid
-                                + ", pendingCallbacks: " + pendingCallbacks);
-                    }
-                    if (pendingCallbacks == null) {
-                        pendingCallbacks = new SparseArray<>();
-                        mPendingCallbackSelfLocked.put(uid, pendingCallbacks);
-                    }
-                    PendingCallback pendingCallback = pendingCallbacks.get(pid);
-                    if (pendingCallback == null) {
-                        pendingCallbacks.put(pid,
-                                new PendingCallback(callbackRecord, displayId, event));
-                    } else {
-                        pendingCallback.addDisplayEvent(displayId, event);
-                    }
-                }
-            } else {
-                callbackRecord.notifyDisplayEventAsync(displayId, event);
-            }
+            deliverEventInternal(callbackRecord, displayId, event);
         }
         mTempCallbacks.clear();
     }
 
+    private void deliverEventInternal(CallbackRecord callbackRecord, int displayId, int event) {
+        final int uid = callbackRecord.mUid;
+        final int pid = callbackRecord.mPid;
+        if (isUidCached(uid)) {
+            // For cached apps, save the pending event until it becomes non-cached
+            synchronized (mPendingCallbackSelfLocked) {
+                SparseArray<PendingCallback> pendingCallbacks = mPendingCallbackSelfLocked.get(
+                    uid);
+                if (extraLogging(callbackRecord.mPackageName)) {
+                    Slog.i(TAG, "Uid is cached: " + uid
+                            + ", pendingCallbacks: " + pendingCallbacks);
+                }
+                if (pendingCallbacks == null) {
+                    pendingCallbacks = new SparseArray<>();
+                    mPendingCallbackSelfLocked.put(uid, pendingCallbacks);
+                }
+                PendingCallback pendingCallback = pendingCallbacks.get(pid);
+                if (pendingCallback == null) {
+                    pendingCallbacks.put(pid,
+                            new PendingCallback(callbackRecord, displayId, event));
+                } else {
+                    pendingCallback.addDisplayEvent(displayId, event);
+                }
+            }
+        } else {
+            callbackRecord.notifyDisplayEventAsync(displayId, event);
+        }
+    }
+
     private boolean extraLogging(String packageName) {
         return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
     }
@@ -3333,6 +3352,7 @@
             pw.println();
             final int displayStateCount = mDisplayStates.size();
             pw.println("Display States: size=" + displayStateCount);
+            pw.println("---------------------");
             for (int i = 0; i < displayStateCount; i++) {
                 final int displayId = mDisplayStates.keyAt(i);
                 final int displayState = mDisplayStates.valueAt(i);
@@ -3348,6 +3368,7 @@
 
             pw.println();
             pw.println("Display Adapters: size=" + mDisplayAdapters.size());
+            pw.println("------------------------");
             for (DisplayAdapter adapter : mDisplayAdapters) {
                 pw.println("  " + adapter.getName());
                 adapter.dumpLocked(ipw);
@@ -3355,6 +3376,7 @@
 
             pw.println();
             pw.println("Display Devices: size=" + mDisplayDeviceRepo.sizeLocked());
+            pw.println("-----------------------");
             mDisplayDeviceRepo.forEachLocked(device -> {
                 pw.println("  " + device.getDisplayDeviceInfoLocked());
                 device.dumpLocked(ipw);
@@ -3366,6 +3388,7 @@
             final int callbackCount = mCallbacks.size();
             pw.println();
             pw.println("Callbacks: size=" + callbackCount);
+            pw.println("-----------------");
             for (int i = 0; i < callbackCount; i++) {
                 CallbackRecord callback = mCallbacks.valueAt(i);
                 pw.println("  " + i + ": mPid=" + callback.mPid
@@ -3378,6 +3401,7 @@
             for (int i = 0; i < displayPowerControllerCount; i++) {
                 mDisplayPowerControllers.valueAt(i).dump(pw);
             }
+
             pw.println();
             mPersistentDataStore.dump(pw);
 
@@ -3396,8 +3420,10 @@
         }
         pw.println();
         mDisplayModeDirector.dump(pw);
+        pw.println();
         mBrightnessSynchronizer.dump(pw);
         if (mSmallAreaDetectionController != null) {
+            pw.println();
             mSmallAreaDetectionController.dump(pw);
         }
 
@@ -3557,7 +3583,7 @@
     }
 
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
-    private DisplayPowerControllerInterface addDisplayPowerControllerLocked(
+    private DisplayPowerController addDisplayPowerControllerLocked(
             LogicalDisplay display) {
         if (mPowerHandler == null) {
             // initPowerManagement has not yet been called.
@@ -3571,7 +3597,7 @@
         final int userSerial = getUserManager().getUserSerialNumber(mContext.getUserId());
         final BrightnessSetting brightnessSetting = new BrightnessSetting(userSerial,
                 mPersistentDataStore, display, mSyncRoot);
-        final DisplayPowerControllerInterface displayPowerController;
+        final DisplayPowerController displayPowerController;
 
         // If display is internal and has a HighBrightnessModeMetadata mapping, use that.
         // Or create a new one and use that.
@@ -3750,7 +3776,7 @@
 
         public boolean mWifiDisplayScanRequested;
 
-        CallbackRecord(int pid, int uid, IDisplayManagerCallback callback,
+        CallbackRecord(int pid, int uid, @NonNull IDisplayManagerCallback callback,
                 @EventsMask long eventsMask) {
             mPid = pid;
             mUid = uid;
@@ -3778,7 +3804,9 @@
         }
 
         /**
-         * @return {@code false} if RemoteException happens; otherwise {@code true} for success.
+         * @return {@code false} if RemoteException happens; otherwise {@code true} for
+         * success.  This returns true even if the event was deferred because the remote client is
+         * cached.
          */
         public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
             if (!shouldSendEvent(event)) {
@@ -3791,9 +3819,19 @@
                             "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsMask="
                                     + mEventsMask);
                 }
+                // The client is not interested in this event, so do nothing.
                 return true;
             }
 
+            return transmitDisplayEvent(displayId, event);
+        }
+
+        /**
+         * Transmit a single display event.  The client is presumed ready.  Return true on success
+         * and false if the client died.
+         */
+        private boolean transmitDisplayEvent(int displayId, @DisplayEvent int event) {
+            // The client is ready to receive the event.
             try {
                 mCallback.onDisplayEvent(displayId, event);
                 return true;
@@ -3805,6 +3843,9 @@
             }
         }
 
+        /**
+         * Return true if the client is interested in this event.
+         */
         private boolean shouldSendEvent(@DisplayEvent int event) {
             final long mask = mEventsMask.get();
             switch (event) {
@@ -4359,7 +4400,7 @@
                                     uniqueId, userSerial);
                     if (config == null) {
                         // Get default configuration
-                        DisplayPowerControllerInterface dpc = getDpcFromUniqueIdLocked(uniqueId);
+                        DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId);
                         if (dpc != null) {
                             config = dpc.getDefaultBrightnessConfiguration();
                         }
@@ -4413,7 +4454,7 @@
                     if (display == null || !display.isEnabledLocked()) {
                         return null;
                     }
-                    DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+                    DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
                     if (dpc != null) {
                         return dpc.getBrightnessInfo();
                     }
@@ -4458,7 +4499,7 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
-                    DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+                    DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
                     if (dpc != null) {
                         dpc.setBrightness(brightness);
                     }
@@ -4478,7 +4519,7 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mSyncRoot) {
-                    DisplayPowerControllerInterface dpc = mDisplayPowerControllers.get(displayId);
+                    DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
                     if (dpc != null) {
                         brightness = dpc.getScreenBrightnessSetting();
                     }
@@ -4805,7 +4846,7 @@
                             id).getPrimaryDisplayDeviceLocked();
                     final int flags = displayDevice.getDisplayDeviceInfoLocked().flags;
                     if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
-                        final DisplayPowerControllerInterface displayPowerController =
+                        final DisplayPowerController displayPowerController =
                                 mDisplayPowerControllers.get(id);
                         if (displayPowerController != null) {
                             ready &= displayPowerController.requestPowerState(request,
@@ -5186,7 +5227,7 @@
                     return null;
                 }
 
-                DisplayPowerControllerInterface displayPowerController =
+                DisplayPowerController displayPowerController =
                         mDisplayPowerControllers.get(logicalDisplay.getDisplayIdLocked());
                 if (displayPowerController == null) {
                     Slog.w(TAG,
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index a188e79..b05a96e 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -39,12 +39,12 @@
 
     @Nullable
     private final DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
-    private final DisplayPowerControllerInterface mDisplayPowerController;
+    private final DisplayPowerController mDisplayPowerController;
     private boolean mIsActive;
 
     public DisplayOffloadSessionImpl(
             @Nullable DisplayManagerInternal.DisplayOffloader displayOffloader,
-            DisplayPowerControllerInterface displayPowerController) {
+            DisplayPowerController displayPowerController) {
         mDisplayOffloader = displayOffloader;
         mDisplayPowerController = displayPowerController;
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index ab79713..bb2bed7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -126,7 +126,7 @@
  * slower by changing the "animator duration scale" option in Development Settings.
  */
 final class DisplayPowerController implements AutomaticBrightnessController.Callbacks,
-        DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface {
+        DisplayWhiteBalanceController.Callbacks{
     private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
     private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
 
@@ -253,10 +253,10 @@
     // The display blanker.
     private final DisplayBlanker mBlanker;
 
-    // The LogicalDisplay tied to this DisplayPowerController2.
+    // The LogicalDisplay tied to this DisplayPowerController.
     private final LogicalDisplay mLogicalDisplay;
 
-    // The ID of the LogicalDisplay tied to this DisplayPowerController2.
+    // The ID of the LogicalDisplay tied to this DisplayPowerController.
     private final int mDisplayId;
 
     // The ID of the display which this display follows for brightness purposes.
@@ -466,7 +466,7 @@
     private ObjectAnimator mColorFadeOffAnimator;
     private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
 
-    // True if this DisplayPowerController2 has been stopped and should no longer be running.
+    // True if this DisplayPowerController has been stopped and should no longer be running.
     private boolean mStopped;
 
     private DisplayDeviceConfig mDisplayDeviceConfig;
@@ -481,7 +481,7 @@
     // DPCs following the brightness of this DPC. This is used in concurrent displays mode - there
     // is one lead display, the additional displays follow the brightness value of the lead display.
     @GuardedBy("mLock")
-    private final SparseArray<DisplayPowerControllerInterface> mDisplayBrightnessFollowers =
+    private final SparseArray<DisplayPowerController> mDisplayBrightnessFollowers =
             new SparseArray();
 
     private boolean mBootCompleted;
@@ -591,7 +591,8 @@
                         mThermalBrightnessThrottlingDataId,
                         logicalDisplay.getPowerThrottlingDataIdLocked(),
                         mDisplayDeviceConfig, displayDeviceInfo.width, displayDeviceInfo.height,
-                        displayToken, mDisplayId), mContext, flags, mSensorManager);
+                        displayToken, mDisplayId), mContext, flags, mSensorManager,
+                        mDisplayBrightnessController.getCurrentBrightness());
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
         mAutomaticBrightnessStrategy =
@@ -678,7 +679,6 @@
     /**
      * Returns true if the proximity sensor screen-off function is available.
      */
-    @Override
     public boolean isProximitySensorAvailable() {
         return mDisplayPowerProximityStateController.isProximitySensorAvailable();
     }
@@ -690,7 +690,6 @@
      * @param includePackage if false will null out the package name in events
      */
     @Nullable
-    @Override
     public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
             @UserIdInt int userId, boolean includePackage) {
         if (mBrightnessTracker == null) {
@@ -699,7 +698,6 @@
         return mBrightnessTracker.getEvents(userId, includePackage);
     }
 
-    @Override
     public void onSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
         Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId, userSerial, newBrightness);
         mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
@@ -736,7 +734,6 @@
     }
 
     @Nullable
-    @Override
     public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
             @UserIdInt int userId) {
         if (mBrightnessTracker == null) {
@@ -748,7 +745,6 @@
     /**
      * Persist the brightness slider events and ambient brightness stats to disk.
      */
-    @Override
     public void persistBrightnessTrackerState() {
         if (mBrightnessTracker != null) {
             mBrightnessTracker.persistBrightnessTrackerState();
@@ -805,7 +801,6 @@
         }
     }
 
-    @Override
     public void overrideDozeScreenState(int displayState, @Display.StateReason int reason) {
         Slog.i(TAG, "New offload doze override: " + Display.stateToString(displayState));
         if (mDisplayOffloadSession != null
@@ -832,7 +827,6 @@
         }
     }
 
-    @Override
     public void setDisplayOffloadSession(DisplayOffloadSession session) {
         if (session == mDisplayOffloadSession) {
             return;
@@ -841,7 +835,6 @@
         mDisplayOffloadSession = session;
     }
 
-    @Override
     public BrightnessConfiguration getDefaultBrightnessConfiguration() {
         if (mAutomaticBrightnessController == null) {
             return null;
@@ -856,12 +849,11 @@
      *
      * Make sure DisplayManagerService.mSyncRoot lock is held when this is called
      */
-    @Override
     public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) {
         mLeadDisplayId = leadDisplayId;
         final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
         if (device == null) {
-            Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
+            Slog.wtf(mTag, "Display Device is null in DisplayPowerController for display: "
                     + mLogicalDisplay.getDisplayIdLocked());
             return;
         }
@@ -935,10 +927,9 @@
     /**
      * Unregisters all listeners and interrupts all running threads; halting future work.
      *
-     * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when
+     * This method should be called when the DisplayPowerController is no longer in use; i.e. when
      * the {@link #mDisplayId display} has been removed.
      */
-    @Override
     public void stop() {
         synchronized (mLock) {
             clearDisplayBrightnessFollowersLocked();
@@ -1215,7 +1206,6 @@
         }
     }
 
-    @Override
     public void setAutomaticScreenBrightnessMode(
             @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
         Message msg = mHandler.obtainMessage();
@@ -1313,7 +1303,7 @@
         boolean mustInitialize = false;
         mBrightnessReasonTemp.set(null);
         mTempBrightnessEvent.reset();
-        SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
+        SparseArray<DisplayPowerController> displayBrightnessFollowers;
         synchronized (mLock) {
             if (mStopped) {
                 return;
@@ -1546,7 +1536,7 @@
         float ambientLux = mAutomaticBrightnessController == null ? 0
                 : mAutomaticBrightnessController.getAmbientLux();
         for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
-            DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
+            DisplayPowerController follower = displayBrightnessFollowers.valueAt(i);
             follower.setBrightnessToFollow(rawBrightnessState,
                     mDisplayBrightnessController.convertToNits(rawBrightnessState),
                     ambientLux, slowChange);
@@ -1588,7 +1578,7 @@
         // brightness sources (such as an app override) are not saved to the setting, but should be
         // reflected in HBM calculations.
         mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
-                mBrightnessClamperController.getBrightnessMaxReason());
+                clampedState.getBrightnessMaxReason());
 
         // Animate the screen brightness when the screen is on or dozing.
         // Skip the animation when the screen is off or suspended.
@@ -1804,7 +1794,7 @@
 
             if (userSetBrightnessChanged
                     || newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
-                logBrightnessEvent(newEvent, unthrottledBrightnessState);
+                logBrightnessEvent(newEvent, unthrottledBrightnessState, clampedState);
             }
             if (mBrightnessEventRingBuffer != null) {
                 mBrightnessEventRingBuffer.append(newEvent);
@@ -1903,7 +1893,6 @@
         }
     }
 
-    @Override
     public void updateBrightness() {
         sendUpdatePowerState();
     }
@@ -1912,12 +1901,10 @@
      * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
      * currently enabled and forcing the screen to be dark.
      */
-    @Override
     public void ignoreProximitySensorUntilChanged() {
         mDisplayPowerProximityStateController.ignoreProximitySensorUntilChanged();
     }
 
-    @Override
     public void setBrightnessConfiguration(BrightnessConfiguration c,
             boolean shouldResetShortTermModel) {
         Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS,
@@ -1925,28 +1912,24 @@
         msg.sendToTarget();
     }
 
-    @Override
     public void setTemporaryBrightness(float brightness) {
         Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS,
                 Float.floatToIntBits(brightness), 0 /*unused*/);
         msg.sendToTarget();
     }
 
-    @Override
     public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
         Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT,
                 Float.floatToIntBits(adjustment), 0 /*unused*/);
         msg.sendToTarget();
     }
 
-    @Override
     public void setBrightnessFromOffload(float brightness) {
         Message msg = mHandler.obtainMessage(MSG_SET_BRIGHTNESS_FROM_OFFLOAD,
                 Float.floatToIntBits(brightness), 0 /*unused*/);
         mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
     }
 
-    @Override
     public float[] getAutoBrightnessLevels(
             @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
         int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -1955,7 +1938,6 @@
         return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
     }
 
-    @Override
     public float[] getAutoBrightnessLuxLevels(
             @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
         int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -1964,7 +1946,6 @@
         return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
     }
 
-    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
@@ -1978,7 +1959,6 @@
         }
     }
 
-    @Override
     public void onBootCompleted() {
         Message msg = mHandler.obtainMessage(MSG_BOOT_COMPLETED);
         mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
@@ -1997,6 +1977,9 @@
         synchronized (mCachedBrightnessInfo) {
             float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX;
             float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX;
+            @BrightnessInfo.BrightnessMaxReason int maxReason =
+                    state != null ? state.getBrightnessMaxReason()
+                            : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
             final float minBrightness = Math.max(stateMin, Math.min(
                     mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
             final float maxBrightness = Math.min(
@@ -2023,7 +2006,7 @@
                             mBrightnessRangeController.getTransitionPoint());
             changed |=
                     mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
-                            mBrightnessClamperController.getBrightnessMaxReason());
+                            maxReason);
             return changed;
         }
     }
@@ -2484,20 +2467,21 @@
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
         mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
                 == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
+        if (screenBrightnessModeSetting == Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) {
+            // In manual mode, all brightness changes should be saved immediately.
+            mDisplayBrightnessController.saveBrightnessIfNeeded();
+        }
     }
 
-
-    @Override
     public float getScreenBrightnessSetting() {
         return mDisplayBrightnessController.getScreenBrightnessSetting();
     }
 
-    @Override
     public float getDozeBrightnessForOffload() {
         return mDisplayBrightnessController.getCurrentBrightness() * mDozeScaleFactor;
     }
 
-    @Override
     public void setBrightness(float brightness) {
         // After HBMController and NBMController migration to Clampers framework
         // currentBrightnessMax should be taken from clampers controller
@@ -2506,7 +2490,6 @@
                 mBrightnessRangeController.getCurrentBrightnessMax());
     }
 
-    @Override
     public void setBrightness(float brightness, int userSerial) {
         // After HBMController and NBMController migration to Clampers framework
         // currentBrightnessMax should be taken from clampers controller
@@ -2515,17 +2498,14 @@
                 mBrightnessRangeController.getCurrentBrightnessMax());
     }
 
-    @Override
     public int getDisplayId() {
         return mDisplayId;
     }
 
-    @Override
     public int getLeadDisplayId() {
         return mLeadDisplayId;
     }
 
-    @Override
     public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
             boolean slowChange) {
         mBrightnessRangeController.onAmbientLuxChange(ambientLux);
@@ -2586,16 +2566,14 @@
                 mAutomaticBrightnessController.getLastSensorTimestamps());
     }
 
-    @Override
-    public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
+    public void addDisplayBrightnessFollower(DisplayPowerController follower) {
         synchronized (mLock) {
             mDisplayBrightnessFollowers.append(follower.getDisplayId(), follower);
             sendUpdatePowerStateLocked();
         }
     }
 
-    @Override
-    public void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
+    public void removeDisplayBrightnessFollower(DisplayPowerController follower) {
         synchronized (mLock) {
             mDisplayBrightnessFollowers.remove(follower.getDisplayId());
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
@@ -2607,7 +2585,7 @@
     @GuardedBy("mLock")
     private void clearDisplayBrightnessFollowersLocked() {
         for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
-            DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
+            DisplayPowerController follower = mDisplayBrightnessFollowers.valueAt(i);
             mHandler.postAtTime(() -> follower.setBrightnessToFollow(
                     PowerManager.BRIGHTNESS_INVALID_FLOAT, BrightnessMappingStrategy.INVALID_NITS,
                     /* ambientLux= */ 0, /* slowChange= */ false), mClock.uptimeMillis());
@@ -2615,11 +2593,12 @@
         mDisplayBrightnessFollowers.clear();
     }
 
-    @Override
     public void dump(final PrintWriter pw) {
         synchronized (mLock) {
             pw.println();
             pw.println("Display Power Controller:");
+            pw.println("-------------------------");
+
             pw.println("  mDisplayId=" + mDisplayId);
             pw.println("  mLeadDisplayId=" + mLeadDisplayId);
             pw.println("  mLightSensor=" + mLightSensor);
@@ -2694,25 +2673,39 @@
                     + mColorFadeOffAnimator.isStarted());
         }
 
+        pw.println();
         if (mPowerState != null) {
             mPowerState.dump(pw);
         }
 
-        if (mAutomaticBrightnessController != null) {
-            mAutomaticBrightnessController.dump(pw);
-            dumpBrightnessEvents(pw);
+        pw.println();
+        if (mDisplayBrightnessController != null) {
+            mDisplayBrightnessController.dump(pw);
         }
 
-        dumpRbcEvents(pw);
-
-        if (mScreenOffBrightnessSensorController != null) {
-            mScreenOffBrightnessSensorController.dump(pw);
+        pw.println();
+        if (mBrightnessClamperController != null) {
+            mBrightnessClamperController.dump(ipw);
         }
 
+        pw.println();
         if (mBrightnessRangeController != null) {
             mBrightnessRangeController.dump(pw);
         }
 
+        pw.println();
+        if (mAutomaticBrightnessController != null) {
+            mAutomaticBrightnessController.dump(pw);
+            dumpBrightnessEvents(pw);
+        }
+        dumpRbcEvents(pw);
+
+        pw.println();
+        if (mScreenOffBrightnessSensorController != null) {
+            mScreenOffBrightnessSensorController.dump(pw);
+        }
+
+        pw.println();
         if (mBrightnessThrottler != null) {
             mBrightnessThrottler.dump(pw);
         }
@@ -2724,24 +2717,13 @@
         }
 
         pw.println();
-
         if (mWakelockController != null) {
             mWakelockController.dumpLocal(pw);
         }
 
         pw.println();
-        if (mDisplayBrightnessController != null) {
-            mDisplayBrightnessController.dump(pw);
-        }
-
-        pw.println();
         if (mDisplayStateController != null) {
-            mDisplayStateController.dumpsys(pw);
-        }
-
-        pw.println();
-        if (mBrightnessClamperController != null) {
-            mBrightnessClamperController.dump(ipw);
+            mDisplayStateController.dump(pw);
         }
     }
 
@@ -2921,7 +2903,8 @@
         return FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_UNKNOWN;
     }
 
-    private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
+    private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness,
+            DisplayBrightnessState brightnessState) {
         int modifier = event.getReason().getModifier();
         int flags = event.getFlags();
         // It's easier to check if the brightness is at maximum level using the brightness
@@ -2958,7 +2941,7 @@
                     event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
                     event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
                     (modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
-                    mBrightnessClamperController.getBrightnessMaxReason(),
+                    brightnessState.getBrightnessMaxReason(),
                     // TODO: (flc) add brightnessMinReason here too.
                     (modifier & BrightnessReason.MODIFIER_DIMMED) > 0,
                     event.isRbcEnabled(),
@@ -3146,19 +3129,17 @@
         }
     }
 
-    @Override
     public void setAutoBrightnessLoggingEnabled(boolean enabled) {
         if (mAutomaticBrightnessController != null) {
             mAutomaticBrightnessController.setLoggingEnabled(enabled);
         }
     }
 
-    @Override // DisplayWhiteBalanceController.Callbacks
+    // DisplayWhiteBalanceController.Callbacks
     public void updateWhiteBalance() {
         sendUpdatePowerState();
     }
 
-    @Override
     public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
         Message msg = mHandler.obtainMessage();
         msg.what = MSG_SET_DWBC_LOGGING_ENABLED;
@@ -3166,7 +3147,6 @@
         msg.sendToTarget();
     }
 
-    @Override
     public void setAmbientColorTemperatureOverride(float cct) {
         Message msg = mHandler.obtainMessage();
         msg.what = MSG_SET_DWBC_COLOR_OVERRIDE;
@@ -3291,10 +3271,10 @@
         BrightnessClamperController getBrightnessClamperController(Handler handler,
                 BrightnessClamperController.ClamperChangeListener clamperChangeListener,
                 BrightnessClamperController.DisplayDeviceData data, Context context,
-                DisplayManagerFlags flags, SensorManager sensorManager) {
+                DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
 
             return new BrightnessClamperController(handler, clamperChangeListener, data, context,
-                    flags, sensorManager);
+                    flags, sensorManager, currentBrightness);
         }
 
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
deleted file mode 100644
index d28578a..0000000
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2022 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.display;
-
-import android.content.pm.ParceledListSlice;
-import android.hardware.display.AmbientBrightnessDayStats;
-import android.hardware.display.BrightnessChangeEvent;
-import android.hardware.display.BrightnessConfiguration;
-import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
-import android.view.Display;
-
-import java.io.PrintWriter;
-
-/**
- * An interface to manage the display's power state and brightness
- */
-public interface DisplayPowerControllerInterface {
-    /**
-     * Notified when the display is changed.
-     *
-     * We use this to apply any changes that might be needed when displays get swapped on foldable
-     * devices, when layouts change, etc.
-     *
-     * Must be called while holding the SyncRoot lock.
-     *
-     * @param hbmInfo The high brightness mode metadata, like
-     *                remaining time and hbm events, for the corresponding
-     *                physical display, to make sure we stay within the safety margins.
-     * @param leadDisplayId The display who is considered our "leader" for things like brightness.
-     */
-    void onDisplayChanged(HighBrightnessModeMetadata hbmInfo, int leadDisplayId);
-
-    /**
-     * Unregisters all listeners and interrupts all running threads; halting future work.
-     *
-     * This method should be called when the DisplayPowerController is no longer in use; i.e. when
-     * the display has been removed.
-     */
-    void stop();
-
-    /**
-     * Used to update the display's BrightnessConfiguration
-     * @param config The new BrightnessConfiguration
-     */
-    void setBrightnessConfiguration(BrightnessConfiguration config,
-            boolean shouldResetShortTermModel);
-
-    /**
-     * Used to set the ambient color temperature of the Display
-     * @param ambientColorTemperature The target ambientColorTemperature
-     */
-    void setAmbientColorTemperatureOverride(float ambientColorTemperature);
-
-    /**
-     * Used to decide the associated AutomaticBrightnessController's BrightnessMode
-     * @param mode The auto-brightness mode
-     */
-    void setAutomaticScreenBrightnessMode(
-            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
-
-    /**
-     * Used to enable/disable the logging of the WhileBalance associated entities
-     * @param enabled Flag which represents if the logging is the be enabled
-     */
-    void setDisplayWhiteBalanceLoggingEnabled(boolean enabled);
-
-    /**
-     * Used to dump the state.
-     * @param writer The PrintWriter used to dump the state.
-     */
-    void dump(PrintWriter writer);
-
-    /**
-     * Used to get the ambient brightness stats
-     */
-    ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(int userId);
-
-    /**
-     * Get the default brightness configuration
-     */
-    BrightnessConfiguration getDefaultBrightnessConfiguration();
-
-    /**
-     * Set the screen brightness of the associated display
-     * @param brightness The value to which the brightness is to be set
-     */
-    void setBrightness(float brightness);
-
-    /**
-     * Set the screen brightness of the associated display
-     * @param brightness The value to which the brightness is to be set
-     * @param userSerial The user for which the brightness value is to be set.
-     */
-    void setBrightness(float brightness, int userSerial);
-
-    /**
-     * Checks if the proximity sensor is available
-     */
-    boolean isProximitySensorAvailable();
-
-    /**
-     * Persist the brightness slider events and ambient brightness stats to disk.
-     */
-    void persistBrightnessTrackerState();
-
-    /**
-     * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
-     * currently enabled and forcing the screen to be dark.
-     */
-    void ignoreProximitySensorUntilChanged();
-
-    /**
-     * Requests a new power state.
-     *
-     * @param request The requested power state.
-     * @param waitForNegativeProximity If true, issues a request to wait for
-     * negative proximity before turning the screen back on,
-     * assuming the screen was turned off by the proximity sensor.
-     * @return True if display is ready, false if there are important changes that must
-     * be made asynchronously.
-     */
-    boolean requestPowerState(DisplayManagerInternal.DisplayPowerRequest request,
-            boolean waitForNegativeProximity);
-
-    /**
-     * Overrides the current doze screen state.
-     *
-     * @param displayState the new doze display state.
-     * @param reason the reason behind the new doze display state.
-     */
-    void overrideDozeScreenState(int displayState, @Display.StateReason int reason);
-
-    void setDisplayOffloadSession(DisplayManagerInternal.DisplayOffloadSession session);
-
-    /**
-     * Sets up the temporary autobrightness adjustment when the user is yet to settle down to a
-     * value.
-     */
-    void setTemporaryAutoBrightnessAdjustment(float adjustment);
-
-    /**
-     * Sets temporary brightness from the offload chip until we get a brightness value from
-     * the light sensor.
-     * @param brightness The brightness value between {@link PowerManager.BRIGHTNESS_MIN} and
-     * {@link PowerManager.BRIGHTNESS_MAX}. Values outside of that range will be ignored.
-     */
-    void setBrightnessFromOffload(float brightness);
-
-    /**
-     * Gets the screen brightness setting
-     */
-    float getScreenBrightnessSetting();
-
-    /**
-     * Gets the brightness value used when the device is in doze
-     */
-    float getDozeBrightnessForOffload();
-
-    /**
-     * Sets up the temporary brightness for the associated display
-     */
-    void setTemporaryBrightness(float brightness);
-
-    /**
-     * Gets the associated {@link BrightnessInfo}
-     */
-    BrightnessInfo getBrightnessInfo();
-
-    /**
-     * Get the {@link BrightnessChangeEvent}s for the specified user.
-     */
-    ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(int userId, boolean hasUsageStats);
-
-    /**
-     * Sets up the logging for the associated {@link AutomaticBrightnessController}
-     * @param enabled Flag to represent if the logging is to be enabled
-     */
-    void setAutoBrightnessLoggingEnabled(boolean enabled);
-
-    /**
-     * Handles the changes to be done to update the brightness when the user is changed
-     * @param newUserId The new userId
-     * @param userSerial The serial number of the new user
-     * @param newBrightness The brightness for the new user
-     */
-    void onSwitchUser(int newUserId, int userSerial, float newBrightness);
-
-    /**
-     * Get the ID of the display associated with this DPC.
-     * @return The display ID
-     */
-    int getDisplayId();
-
-    /**
-     * Get the ID of the display that is the leader of this DPC.
-     *
-     * Note that this is different than the display associated with the DPC. The leader is another
-     * display which we follow for things like brightness.
-     *
-     * Must be called while holding the SyncRoot lock.
-     */
-    int getLeadDisplayId();
-
-    /**
-     * Set the brightness to follow if this is an additional display in a set of concurrent
-     * displays.
-     * @param leadDisplayBrightness The brightness of the lead display in the set of concurrent
-     *                              displays
-     * @param nits The brightness value in nits if the device supports nits. Set to a negative
-     *             number otherwise.
-     * @param ambientLux The lux value that will be passed to {@link HighBrightnessModeController}
-     * @param slowChange Indicates whether we should slowly animate to the given brightness value.
-     */
-    void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux,
-            boolean slowChange);
-
-    /**
-     * Add an additional display that will copy the brightness value from this display. This is used
-     * when the device is in concurrent displays mode.
-     * @param follower The DPC that should copy the brightness value from this DPC
-     */
-    void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower);
-
-    /**
-     * Removes the given display from the list of brightness followers.
-     * @param follower The DPC to remove from the followers list
-     */
-    void removeDisplayBrightnessFollower(DisplayPowerControllerInterface follower);
-
-    /**
-     * Indicate that boot has been completed and the screen is ready to update.
-     */
-    void onBootCompleted();
-
-    /**
-     * Get the brightness levels used to determine automatic brightness based on lux levels.
-     * @param mode The auto-brightness mode
-     * @return The brightness levels for the specified mode. The values are between
-     * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
-     */
-    float[] getAutoBrightnessLevels(
-            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
-
-    /**
-     * Get the lux levels used to determine automatic brightness.
-     * @param mode The auto-brightness mode
-     * @return The lux levels for the specified mode
-     */
-    float[] getAutoBrightnessLuxLevels(
-            @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
-}
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index 882c02f..215932c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -315,6 +315,7 @@
     public void dumpLocal(PrintWriter pw) {
         pw.println();
         pw.println("DisplayPowerProximityStateController:");
+        pw.println("-------------------------------------");
         synchronized (mLock) {
             pw.println("  mPendingWaitForNegativeProximityLocked="
                     + mPendingWaitForNegativeProximityLocked);
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index e5efebc..2fbb114 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -344,7 +344,6 @@
     }
 
     public void dump(PrintWriter pw) {
-        pw.println();
         pw.println("Display Power State:");
         pw.println("  mStopped=" + mStopped);
         pw.println("  mScreenState=" + Display.stateToString(mScreenState));
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 6e0b9cf..0570b2a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -1286,7 +1286,10 @@
                 pw.println("  " + mSupportedModes.valueAt(i));
             }
             pw.println("mSupportedColorModes=" + mSupportedColorModes);
-            pw.println("mDisplayDeviceConfig=" + mDisplayDeviceConfig);
+            pw.println("");
+            pw.println("DisplayDeviceConfig: ");
+            pw.println("---------------------");
+            pw.println(mDisplayDeviceConfig);
         }
 
         private int findSfDisplayModeIdLocked(int displayModeId, int modeGroup) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 5d55d190..e8be8a4 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -1040,6 +1040,9 @@
 
     public void dumpLocked(PrintWriter pw) {
         pw.println("mDisplayId=" + mDisplayId);
+        pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null
+                ? mPrimaryDisplayDevice.getNameLocked() + "(" + mPrimaryDisplayDevice.getUniqueId()
+                        + ")" : "null"));
         pw.println("mIsEnabled=" + mIsEnabled);
         pw.println("mIsInTransition=" + mIsInTransition);
         pw.println("mLayerStack=" + mLayerStack);
@@ -1049,8 +1052,6 @@
         pw.println("mRequestedColorMode=" + mRequestedColorMode);
         pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")");
         pw.println("mDisplayScalingDisabled=" + mDisplayScalingDisabled);
-        pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ?
-                mPrimaryDisplayDevice.getNameLocked() : "null"));
         pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo);
         pw.println("mOverrideDisplayInfo=" + mOverrideDisplayInfo);
         pw.println("mRequestedMinimalPostProcessing=" + mRequestedMinimalPostProcessing);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index e9ecfc6..c3f6a52 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -427,6 +427,7 @@
 
     public void dumpLocked(PrintWriter pw) {
         pw.println("LogicalDisplayMapper:");
+        pw.println("---------------------");
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.increaseIndent();
 
@@ -477,14 +478,14 @@
             // The boot animation might still be in progress, we do not want to switch states now
             // as the boot animation would end up with an incorrect size.
             if (DEBUG) {
-                Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState
-                        + " until boot is completed");
+                Slog.d(TAG, "Postponing transition to state: "
+                        + mPendingDeviceState.getIdentifier() + " until boot is completed");
             }
             mDeviceStateToBeAppliedAfterBoot = state;
             return;
         }
 
-        Slog.i(TAG, "Requesting Transition to state: " + state + ", from state="
+        Slog.i(TAG, "Requesting Transition to state: " + state.getIdentifier() + ", from state="
                 + mDeviceState.getIdentifier() + ", interactive=" + mInteractive
                 + ", mBootCompleted=" + mBootCompleted);
         // As part of a state transition, we may need to turn off some displays temporarily so that
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 2d7792d..9cdc918 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -628,7 +628,9 @@
     }
 
     public void dump(PrintWriter pw) {
-        pw.println("PersistentDataStore");
+        pw.println("PersistentDataStore:");
+        pw.println("--------------------");
+
         pw.println("  mLoaded=" + mLoaded);
         pw.println("  mDirty=" + mDirty);
         pw.println("  RememberedWifiDisplays:");
diff --git a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
index 0a884c9..b63eba3 100644
--- a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
+++ b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
@@ -121,6 +121,7 @@
     /** Dump current state */
     public void dump(PrintWriter pw) {
         pw.println("Screen Off Brightness Sensor Controller:");
+        pw.println("----------------------------------------");
         IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
         idpw.increaseIndent();
         idpw.println("registered=" + mRegistered);
diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
index bf384b0..3ed7e57 100644
--- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java
+++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
@@ -133,7 +133,8 @@
     }
 
     void dump(PrintWriter pw) {
-        pw.println("Small area detection allowlist");
+        pw.println("Small area detection allowlist:");
+        pw.println("-------------------------------");
         pw.println("  Packages:");
         synchronized (mLock) {
             for (String pkg : mAllowPkgMap.keySet()) {
diff --git a/services/core/java/com/android/server/display/WakelockController.java b/services/core/java/com/android/server/display/WakelockController.java
index 5b0229c..3520723 100644
--- a/services/core/java/com/android/server/display/WakelockController.java
+++ b/services/core/java/com/android/server/display/WakelockController.java
@@ -417,6 +417,7 @@
      */
     public void dumpLocal(PrintWriter pw) {
         pw.println("WakelockController State:");
+        pw.println("-------------------------");
         pw.println("  mDisplayId=" + mDisplayId);
         pw.println("  mUnfinishedBusiness=" + hasUnfinishedBusiness());
         pw.println("  mOnStateChangePending=" + isOnStateChangedPending());
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index e157b05..72a91d5 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -339,6 +339,13 @@
     }
 
     /**
+     * Flush the brightness update that has been made to the persistent data store.
+     */
+    public void saveBrightnessIfNeeded() {
+        mBrightnessSetting.saveIfNeeded();
+    }
+
+    /**
      * Sets the current screen brightness, and notifies the BrightnessSetting about the change.
      */
     public void updateScreenBrightnessSetting(float brightnessValue, float maxBrightness) {
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 1b49bbc..06890e7 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -262,6 +262,7 @@
     public void dump(PrintWriter writer) {
         writer.println();
         writer.println("DisplayBrightnessStrategySelector:");
+        writer.println("----------------------------------");
         writer.println("  mDisplayId= " + mDisplayId);
         writer.println("  mOldBrightnessStrategyName= " + mOldBrightnessStrategyName);
         writer.println(
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 59fffe7..d3be33f 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -75,10 +75,13 @@
     private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState();
 
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
+    private final DisplayManagerFlags mDisplayManagerFlags;
     private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
 
     private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
     @Nullable
+    private BrightnessPowerClamper mPowerClamper;
+    @Nullable
     private Type mClamperType = null;
 
     private boolean mClamperApplied = false;
@@ -93,16 +96,18 @@
 
     public BrightnessClamperController(Handler handler,
             ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
-            DisplayManagerFlags flags, SensorManager sensorManager) {
-        this(new Injector(), handler, clamperChangeListener, data, context, flags, sensorManager);
+            DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
+        this(new Injector(), handler, clamperChangeListener, data, context, flags, sensorManager,
+                currentBrightness);
     }
 
     @VisibleForTesting
     BrightnessClamperController(Injector injector, Handler handler,
             ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
-            DisplayManagerFlags flags, SensorManager sensorManager) {
+            DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
         mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
         mHandler = handler;
+        mDisplayManagerFlags = flags;
         mLightSensorController = injector.getLightSensorController(sensorManager, context,
                 mLightSensorListener, mHandler);
 
@@ -117,7 +122,15 @@
         };
 
         mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
-                context);
+                context, currentBrightness);
+        if (mDisplayManagerFlags.isPowerThrottlingClamperEnabled()) {
+            for (BrightnessClamper clamper: mClampers) {
+                if (clamper.getType() == Type.POWER) {
+                    mPowerClamper = (BrightnessPowerClamper) clamper;
+                    break;
+                }
+            }
+        }
         mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
                 data);
 
@@ -161,6 +174,7 @@
         builder.setBrightness(cappedBrightness);
         builder.setMaxBrightness(mBrightnessCap);
         builder.setCustomAnimationRate(mCustomAnimationRate);
+        builder.setBrightnessMaxReason(getBrightnessMaxReason());
 
         if (mClamperType != null) {
             builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -182,22 +196,17 @@
             mModifiers.get(i).apply(request, builder);
         }
 
+        if (mDisplayManagerFlags.isPowerThrottlingClamperEnabled()) {
+            if (mPowerClamper != null) {
+                mPowerClamper.updateCurrentBrightness(cappedBrightness);
+            }
+        }
+
         return builder.build();
     }
 
-    /**
-     * See BrightnessThrottler.getBrightnessMaxReason:
-     * used in:
-     * 1) DPC2.CachedBrightnessInfo to determine changes
-     * 2) DPC2.logBrightnessEvent
-     * 3) HBMController - for logging
-     * Method is called in mHandler thread (DisplayControllerHandler), in the same thread
-     * recalculateBrightnessCap and DPC2.updatePowerStateInternal are called.
-     * Should be moved to DisplayBrightnessState OR derived from DisplayBrightnessState
-     * TODO: b/263362199
-     */
     @BrightnessInfo.BrightnessMaxReason
-    public int getBrightnessMaxReason() {
+    private int getBrightnessMaxReason() {
         if (mClamperType == null) {
             return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
         } else if (mClamperType == Type.THERMAL) {
@@ -224,6 +233,7 @@
      */
     public void dump(PrintWriter writer) {
         writer.println("BrightnessClamperController:");
+        writer.println("----------------------------");
         writer.println("  mBrightnessCap: " + mBrightnessCap);
         writer.println("  mClamperType: " + mClamperType);
         writer.println("  mClamperApplied: " + mClamperApplied);
@@ -321,13 +331,17 @@
 
         List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler,
                 ClamperChangeListener clamperChangeListener, DisplayDeviceData data,
-                DisplayManagerFlags flags, Context context) {
+                DisplayManagerFlags flags, Context context, float currentBrightness) {
             List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();
             clampers.add(
                     new BrightnessThermalClamper(handler, clamperChangeListener, data));
             if (flags.isPowerThrottlingClamperEnabled()) {
-                clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
-                        data));
+                // Check if power-throttling config is present.
+                PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData();
+                if (configData != null) {
+                    clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
+                            data, currentBrightness));
+                }
             }
             if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
                 clampers.add(new BrightnessWearBedtimeModeClamper(handler, context,
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
index 790322d..85e81f9 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
@@ -21,16 +21,23 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
 import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.Temperature;
 import android.provider.DeviceConfigInterface;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData;
 import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;
 import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel;
+import com.android.server.display.brightness.BrightnessUtils;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.utils.DeviceConfigParsingUtils;
 
@@ -65,14 +72,21 @@
     private PowerThrottlingData mPowerThrottlingDataActive = null;
     @Nullable
     private PowerThrottlingConfigData mPowerThrottlingConfigData = null;
-
+    @NonNull
+    private final ThermalLevelListener mThermalLevelListener;
+    @NonNull
+    private final PowerChangeListener mPowerChangeListener;
     private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE;
+    private boolean mCurrentThermalLevelChanged = false;
     private float mCurrentAvgPowerConsumed = 0;
     @Nullable
     private String mUniqueDisplayId = null;
     @Nullable
     private String mDataId = null;
-
+    private float mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
+    private float mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+    private float mCustomAnimationRateSecDeviceConfig =
+                        DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
     private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
         try {
             int status = DeviceConfigParsingUtils.parseThermalStatus(key);
@@ -88,23 +102,41 @@
 
 
     BrightnessPowerClamper(Handler handler, ClamperChangeListener listener,
-            PowerData powerData) {
-        this(new Injector(), handler, listener, powerData);
+            PowerData powerData, float currentBrightness) {
+        this(new Injector(), handler, listener, powerData, currentBrightness);
     }
 
     @VisibleForTesting
     BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener,
-            PowerData powerData) {
+                           PowerData powerData, float currentBrightness) {
         super(handler, listener);
         mInjector = injector;
-        mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
+        mCurrentBrightness = currentBrightness;
+        mPowerChangeListener = (powerConsumed, thermalStatus) -> {
+            recalculatePowerQuotaChange(powerConsumed, thermalStatus);
+        };
+        mPowerThrottlingConfigData = powerData.getPowerThrottlingConfigData();
+        if (mPowerThrottlingConfigData != null) {
+            mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec;
+        }
+        mThermalLevelListener = new ThermalLevelListener(handler);
+        mPmicMonitor =
+            mInjector.getPmicMonitor(mPowerChangeListener,
+                    mThermalLevelListener.getThermalService(),
+                    mPowerThrottlingConfigData.pollingWindowMaxMillis,
+                    mPowerThrottlingConfigData.pollingWindowMinMillis);
 
+        mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
         mHandler.post(() -> {
             setDisplayData(powerData);
             loadOverrideData();
             start();
         });
+    }
 
+    @VisibleForTesting
+    PowerChangeListener getPowerChangeListener() {
+        return mPowerChangeListener;
     }
 
     @Override
@@ -114,6 +146,11 @@
     }
 
     @Override
+    float getCustomAnimationRate() {
+        return mCustomAnimationRateSec;
+    }
+
+    @Override
     void onDeviceConfigChanged() {
         mHandler.post(() -> {
             loadOverrideData();
@@ -134,6 +171,9 @@
         if (mPmicMonitor != null) {
             mPmicMonitor.shutdown();
         }
+        if (mThermalLevelListener != null) {
+            mThermalLevelListener.stop();
+        }
     }
 
     /**
@@ -144,11 +184,20 @@
         pw.println("  mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed);
         pw.println("  mUniqueDisplayId=" + mUniqueDisplayId);
         pw.println("  mCurrentThermalLevel=" + mCurrentThermalLevel);
+        pw.println("  mCurrentThermalLevelChanged=" + mCurrentThermalLevelChanged);
         pw.println("  mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null"
                 : mPowerThrottlingDataFromDDC.toString()));
+        mThermalLevelListener.dump(pw);
         super.dump(pw);
     }
 
+    /**
+     * Updates current brightness, for power calculations.
+     */
+    public void updateCurrentBrightness(float currentBrightness) {
+        mCurrentBrightness = currentBrightness;
+    }
+
     private void recalculateActiveData() {
         if (mUniqueDisplayId == null || mDataId == null) {
             return;
@@ -156,17 +205,11 @@
         mPowerThrottlingDataActive = mPowerThrottlingDataOverride
                 .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
                         mPowerThrottlingDataFromDDC);
-        if (mPowerThrottlingDataActive != null) {
-            if (mPmicMonitor != null) {
-                mPmicMonitor.stop();
-                mPmicMonitor.start();
-            }
-        } else {
+        if (mPowerThrottlingDataActive == null) {
             if (mPmicMonitor != null) {
                 mPmicMonitor.stop();
             }
         }
-        recalculateBrightnessCap();
     }
 
     private void loadOverrideData() {
@@ -198,21 +241,57 @@
         if (mPowerThrottlingDataActive == null) {
             return;
         }
-        if (powerQuota > 0 && mCurrentAvgPowerConsumed > powerQuota) {
-            isActive = true;
-            // calculate new brightness Cap.
-            // Brightness has a linear relation to power-consumed.
-            targetBrightnessCap =
-                    (powerQuota / mCurrentAvgPowerConsumed) * PowerManager.BRIGHTNESS_MAX;
-            // Cap to lowest allowed brightness on device.
+        if (powerQuota > 0) {
+            if (BrightnessUtils.isValidBrightnessValue(mCurrentBrightness)
+                    && (mCurrentAvgPowerConsumed > powerQuota)) {
+                isActive = true;
+                // calculate new brightness Cap.
+                // Brightness has a linear relation to power-consumed.
+                targetBrightnessCap =
+                    (powerQuota / mCurrentAvgPowerConsumed) * mCurrentBrightness;
+            } else if (mCurrentThermalLevelChanged) {
+                if (mCurrentThermalLevel == Temperature.THROTTLING_NONE) {
+                    // reset pmic and remove the power-throttling cap.
+                    isActive = true;
+                    targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+                    mPmicMonitor.stop();
+                } else {
+                    isActive = true;
+                    // Since the thermal status has changed, we need to remove power-throttling cap.
+                    // Instead of recalculating and changing brightness again, adding flicker,
+                    // we will wait for the next pmic cycle to re-evaluate this value
+                    // make act on it, if needed.
+                    targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+                    if (mPmicMonitor.isStopped()) {
+                        mPmicMonitor.start();
+                    }
+                }
+            } else { // Current power consumed is under the quota.
+                isActive = true;
+                targetBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+            }
+        }
+
+        // Cap to lowest allowed brightness on device.
+        if (mPowerThrottlingConfigData != null) {
             targetBrightnessCap = Math.max(targetBrightnessCap,
-                    mPowerThrottlingConfigData.brightnessLowestCapAllowed);
+                                mPowerThrottlingConfigData.brightnessLowestCapAllowed);
         }
 
         if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) {
             mIsActive = isActive;
+            Slog.i(TAG, "Power clamper changing current brightness cap mBrightnessCap: "
+                    + mBrightnessCap + " to target brightness cap:" + targetBrightnessCap
+                    + " for current screen brightness: " + mCurrentBrightness);
             mBrightnessCap = targetBrightnessCap;
+            Slog.i(TAG, "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel
+                    + " mCurrentThermalLevelChanged:" + mCurrentThermalLevelChanged
+                    + " mCurrentAvgPowerConsumed:" + mCurrentAvgPowerConsumed
+                    + " mCustomAnimationRateSec:" + mCustomAnimationRateSecDeviceConfig);
+            mCustomAnimationRateSec = mCustomAnimationRateSecDeviceConfig;
             mChangeListener.onChanged();
+        } else {
+            mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
         }
     }
 
@@ -234,6 +313,11 @@
 
     private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) {
         mHandler.post(() -> {
+            if (mCurrentThermalLevel != thermalStatus) {
+                mCurrentThermalLevelChanged = true;
+            } else {
+                mCurrentThermalLevelChanged = false;
+            }
             mCurrentThermalLevel = thermalStatus;
             mCurrentAvgPowerConsumed = avgPowerConsumed;
             recalculateBrightnessCap();
@@ -244,14 +328,107 @@
         if (mPowerThrottlingConfigData == null) {
             return;
         }
-        PowerChangeListener listener = (powerConsumed, thermalStatus) -> {
-            recalculatePowerQuotaChange(powerConsumed, thermalStatus);
-        };
-        mPmicMonitor =
-            mInjector.getPmicMonitor(listener, mPowerThrottlingConfigData.pollingWindowMillis);
+        if (mPowerThrottlingConfigData.pollingWindowMaxMillis
+                <= mPowerThrottlingConfigData.pollingWindowMinMillis) {
+            Slog.e(TAG, "Brightness power max polling window:"
+                    + mPowerThrottlingConfigData.pollingWindowMaxMillis
+                    + " msec, should be greater than brightness min polling window:"
+                    + mPowerThrottlingConfigData.pollingWindowMinMillis + " msec.");
+            return;
+        }
+        if ((mPowerThrottlingConfigData.pollingWindowMaxMillis
+                % mPowerThrottlingConfigData.pollingWindowMinMillis) != 0) {
+            Slog.e(TAG, "Brightness power max polling window:"
+                    + mPowerThrottlingConfigData.pollingWindowMaxMillis
+                    + " msec, is not divisible by brightness min polling window:"
+                    + mPowerThrottlingConfigData.pollingWindowMinMillis + " msec.");
+            return;
+        }
+        mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec;
+        mThermalLevelListener.start();
+    }
+
+    private void activatePmicMonitor() {
+        if (!mPmicMonitor.isStopped()) {
+            return;
+        }
         mPmicMonitor.start();
     }
 
+    private void deactivatePmicMonitor(@Temperature.ThrottlingStatus int status) {
+        if (status != Temperature.THROTTLING_NONE) {
+            return;
+        }
+        if (mPmicMonitor.isStopped()) {
+            return;
+        }
+        mPmicMonitor.stop();
+    }
+
+    private final class ThermalLevelListener extends IThermalEventListener.Stub {
+        private final Handler mHandler;
+        private IThermalService mThermalService;
+        private boolean mStarted;
+
+        ThermalLevelListener(Handler handler) {
+            mHandler = handler;
+            mStarted = false;
+            mThermalService = IThermalService.Stub.asInterface(
+                    ServiceManager.getService(Context.THERMAL_SERVICE));
+        }
+
+        IThermalService getThermalService() {
+            return mThermalService;
+        }
+
+        void start() {
+            if (mStarted) {
+                return;
+            }
+            if (mThermalService == null) {
+                return;
+            }
+            try {
+                // TODO b/279114539 Try DISPLAY first and then fallback to SKIN.
+                mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+                mStarted = true;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to register thermal status listener", e);
+            }
+        }
+
+        @Override
+        public void notifyThrottling(Temperature temp) {
+            @Temperature.ThrottlingStatus int status = temp.getStatus();
+            if (status >= Temperature.THROTTLING_LIGHT) {
+                Slog.d(TAG, "Activating pmic monitor due to thermal state:" + status);
+                mHandler.post(() -> activatePmicMonitor());
+            } else {
+                if (!mPmicMonitor.isStopped()) {
+                    mHandler.post(() -> deactivatePmicMonitor(status));
+                }
+            }
+        }
+
+        void stop() {
+            if (!mStarted) {
+                return;
+            }
+            try {
+                mThermalService.unregisterThermalEventListener(this);
+                mStarted = false;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to unregister thermal status listener", e);
+            }
+            mThermalService = null;
+        }
+
+        void dump(PrintWriter writer) {
+            writer.println("  ThermalLevelObserver:");
+            writer.println("    mStarted: " + mStarted);
+        }
+    }
+
     public interface PowerData {
         @NonNull
         String getUniqueDisplayId();
@@ -279,8 +456,12 @@
 
     @VisibleForTesting
     static class Injector {
-        PmicMonitor getPmicMonitor(PowerChangeListener listener, int pollingTime) {
-            return new PmicMonitor(listener, pollingTime);
+        PmicMonitor getPmicMonitor(PowerChangeListener powerChangeListener,
+                                   IThermalService thermalService,
+                                   int pollingMaxTimeMillis,
+                                   int pollingMinTimeMillis) {
+            return new PmicMonitor(powerChangeListener, thermalService, pollingMaxTimeMillis,
+                                        pollingMinTimeMillis);
         }
 
         DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
index 26784f23..355f4fe 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java
@@ -18,15 +18,12 @@
 
 import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.hardware.power.stats.EnergyConsumer;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.IThermalService;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.Temperature;
 import android.power.PowerStatsInternal;
 import android.util.IntArray;
@@ -51,25 +48,30 @@
 
     // The executor to periodically monitor the display power.
     private final ScheduledExecutorService mExecutor;
-    @NonNull
-    private final PowerChangeListener mPowerChangeListener;
-    private final long mPowerMonitorPeriodConfigSecs;
+    private final long mPowerMonitorPeriodConfigMillis;
     private final PowerStatsInternal mPowerStatsInternal;
     @VisibleForTesting final IThermalService mThermalService;
+    @VisibleForTesting PowerChangeListener mPowerChangeListener;
     private ScheduledFuture<?> mPmicMonitorFuture;
     private float mLastEnergyConsumed = 0;
-    private float mCurrentAvgPower = 0;
+    private float mCurrentTotalAvgPower = 0;
     private Temperature mCurrentTemperature;
     private long mCurrentTimestampMillis = 0;
+    private float[] mAvgPowerCircularList;
+    private int mPowerListStart = 0;
+    private int mPowerListEnd = 0;
 
-    PmicMonitor(PowerChangeListener listener, int powerMonitorPeriodConfigSecs) {
+    PmicMonitor(PowerChangeListener listener,
+                IThermalService thermalService,
+                int pollingMaxTimeMillis,
+                int pollingMinTimeMillis) {
         mPowerChangeListener = listener;
+        mThermalService = thermalService;
         mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
-        mThermalService = IThermalService.Stub.asInterface(
-                ServiceManager.getService(Context.THERMAL_SERVICE));
+        mAvgPowerCircularList = new float[pollingMaxTimeMillis / pollingMinTimeMillis];
         // start a periodic worker thread.
         mExecutor = Executors.newSingleThreadScheduledExecutor();
-        mPowerMonitorPeriodConfigSecs = (long) powerMonitorPeriodConfigSecs;
+        mPowerMonitorPeriodConfigMillis = pollingMinTimeMillis;
     }
 
     @Nullable
@@ -141,12 +143,28 @@
 
         // capture thermal state.
         Temperature displayTemperature = getDisplayTemperature();
-        mCurrentAvgPower = currentPower;
+        boolean isBufferFull = false;
+        mAvgPowerCircularList[mPowerListEnd] = currentPower;
+        mCurrentTotalAvgPower += currentPower;
+        mPowerListEnd =
+            (mPowerListEnd + 1) % mAvgPowerCircularList.length;
+        if (mPowerListStart == mPowerListEnd) {
+            isBufferFull = true;
+        }
+
         mCurrentTemperature = displayTemperature;
         mLastEnergyConsumed = displayResults[0].energyUWs;
         mCurrentTimestampMillis = displayResults[0].timestampMs;
-        if (mCurrentTemperature != null) {
-            mPowerChangeListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus());
+
+        if (mCurrentTemperature != null && isBufferFull) {
+            mPowerChangeListener.onChanged(mCurrentTotalAvgPower / mAvgPowerCircularList.length,
+                                            mCurrentTemperature.getStatus());
+        }
+
+        // average power long-term list is full, reset values for next cycle.
+        if (isBufferFull) {
+            mCurrentTotalAvgPower = mCurrentTotalAvgPower - mAvgPowerCircularList[mPowerListStart];
+            mPowerListStart = (mPowerListStart + 1) % mAvgPowerCircularList.length;
         }
     }
 
@@ -165,11 +183,11 @@
         if (mPmicMonitorFuture == null) {
             mPmicMonitorFuture = mExecutor.scheduleAtFixedRate(
                                     this::capturePeriodicDisplayPower,
-                                    mPowerMonitorPeriodConfigSecs,
-                                    mPowerMonitorPeriodConfigSecs,
-                                    TimeUnit.SECONDS);
+                                    mPowerMonitorPeriodConfigMillis,
+                                    mPowerMonitorPeriodConfigMillis,
+                                    TimeUnit.MILLISECONDS);
         } else {
-            Slog.e(TAG, "already scheduled, stop() called before start.");
+            Slog.e(TAG, "PMIC already scheduled, stop() called before start.");
         }
     }
 
@@ -184,6 +202,23 @@
     }
 
     /**
+     * Updates PMIC configuration.
+     */
+    public void updateConfiguration() {
+        if (mPmicMonitorFuture != null) {
+            mPmicMonitorFuture.cancel(true);
+            mPmicMonitorFuture = null;
+        }
+    }
+
+    /**
+     * Returns if PMIC monitor is stopped.
+     */
+    public boolean isStopped() {
+        return mPmicMonitorFuture == null;
+    }
+
+    /**
      * Shutdown power IC service and worker thread.
      */
     public void shutdown() {
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java
index 1db9bbe..91c985830 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java
@@ -100,6 +100,7 @@
         writer.println("AutoBrightnessFallbackStrategy:");
         writer.println("  mLeadDisplayId=" + mLeadDisplayId);
         writer.println("  mIsDisplayEnabled=" + mIsDisplayEnabled);
+        writer.println("");
         if (mScreenOffBrightnessSensorController != null) {
             IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
             mScreenOffBrightnessSensorController.dump(ipw);
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 35be0f3..69b67c8 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -420,6 +420,7 @@
      */
     public void dump(PrintWriter pw) {
         pw.println("DisplayManagerFlags:");
+        pw.println("--------------------");
         pw.println(" " + mAdaptiveToneImprovements1);
         pw.println(" " + mAdaptiveToneImprovements2);
         pw.println(" " + mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState);
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..18e0d6e 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -578,7 +578,8 @@
      * @param pw The stream to dump information to.
      */
     public void dump(PrintWriter pw) {
-        pw.println("DisplayModeDirector");
+        pw.println("DisplayModeDirector:");
+        pw.println("--------------------");
         synchronized (mLock) {
             pw.println("  mSupportedModesByDisplay:");
             for (int i = 0; i < mSupportedModesByDisplay.size(); i++) {
@@ -1500,10 +1501,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/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
index 21bb208..dba6874 100644
--- a/services/core/java/com/android/server/display/state/DisplayStateController.java
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -114,9 +114,9 @@
      *
      * @param pw The PrintWriter used to dump the state.
      */
-    public void dumpsys(PrintWriter pw) {
-        pw.println();
+    public void dump(PrintWriter pw) {
         pw.println("DisplayStateController:");
+        pw.println("-----------------------");
         pw.println("  mPerformScreenOffTransition:" + mPerformScreenOffTransition);
         pw.println("  mDozeStateOverride=" + mDozeStateOverride);
 
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 7ea576d..49c45a7 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -427,7 +427,8 @@
      *      The writer used to dump the state.
      */
     public void dump(PrintWriter writer) {
-        writer.println("DisplayWhiteBalanceController");
+        writer.println("DisplayWhiteBalanceController:");
+        writer.println("------------------------------");
         writer.println("  mLoggingEnabled=" + mLoggingEnabled);
         writer.println("  mEnabled=" + mEnabled);
         writer.println("  mStrongModeEnabled=" + mStrongModeEnabled);
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceSettings.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceSettings.java
index 0efb749..a5755ac 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceSettings.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceSettings.java
@@ -23,7 +23,6 @@
 import android.os.Message;
 import android.util.Slog;
 
-import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
@@ -129,7 +128,8 @@
      *      The writer used to dump the state.
      */
     public void dump(PrintWriter writer) {
-        writer.println("DisplayWhiteBalanceSettings");
+        writer.println("DisplayWhiteBalanceSettings:");
+        writer.println("----------------------------");
         writer.println("  mLoggingEnabled=" + mLoggingEnabled);
         writer.println("  mContext=" + mContext);
         writer.println("  mHandler=" + mHandler);
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/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 1220542..52bf537 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -597,9 +597,6 @@
         mKeyRemapper.systemRunning();
         mPointerIconCache.systemRunning();
         mKeyboardGlyphManager.systemRunning();
-        if (mTouchpadDebugViewController != null) {
-            mTouchpadDebugViewController.systemRunning();
-        }
     }
 
     private void reloadDeviceAliases() {
@@ -3340,6 +3337,13 @@
         }
     }
 
+    void updateTouchpadVisualizerEnabled(boolean enabled) {
+        mNative.setShouldNotifyTouchpadHardwareState(enabled);
+        if (mTouchpadDebugViewController != null) {
+            mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(enabled);
+        }
+    }
+
     void updatePointerLocationEnabled(boolean enabled) {
         mWindowManagerCallbacks.notifyPointerLocationChanged(enabled);
     }
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index ef61d02..835fb72 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -180,7 +180,7 @@
     }
 
     private void updateTouchpadHardwareStateNotificationsEnabled() {
-        mNative.setShouldNotifyTouchpadHardwareState(InputSettings.useTouchpadVisualizer(mContext));
+        mService.updateTouchpadVisualizerEnabled(InputSettings.useTouchpadVisualizer(mContext));
     }
 
     private void updateTouchpadRightClickZoneEnabled() {
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index 5fca771..7785ffb 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -16,23 +16,69 @@
 
 package com.android.server.input.debug;
 
+import android.annotation.NonNull;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.Slog;
 import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-public class TouchpadDebugView extends LinearLayout {
+import java.util.Objects;
 
+public class TouchpadDebugView extends LinearLayout {
     /**
      * Input device ID for the touchpad that this debug view is displaying.
      */
     private final int mTouchpadId;
 
+    @NonNull
+    private final WindowManager mWindowManager;
+
+    @NonNull
+    private final WindowManager.LayoutParams mWindowLayoutParams;
+
+    private final int mTouchSlop;
+
+    private float mTouchDownX;
+    private float mTouchDownY;
+    private int mScreenWidth;
+    private int mScreenHeight;
+    private int mWindowLocationBeforeDragX;
+    private int mWindowLocationBeforeDragY;
+
     public TouchpadDebugView(Context context, int touchpadId) {
         super(context);
         mTouchpadId = touchpadId;
+        mWindowManager =
+                Objects.requireNonNull(getContext().getSystemService(WindowManager.class));
         init(context);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+
+        // TODO(b/360137366): Use the hardware properties to initialise layout parameters.
+        mWindowLayoutParams = new WindowManager.LayoutParams();
+        mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+        mWindowLayoutParams.privateFlags |=
+                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        mWindowLayoutParams.setFitInsetsTypes(0);
+        mWindowLayoutParams.layoutInDisplayCutoutMode =
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mWindowLayoutParams.format = PixelFormat.TRANSLUCENT;
+        mWindowLayoutParams.setTitle("TouchpadDebugView - display " + mContext.getDisplayId());
+
+        mWindowLayoutParams.x = 40;
+        mWindowLayoutParams.y = 100;
+        mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+        mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
     }
 
     private void init(Context context) {
@@ -43,14 +89,12 @@
         setBackgroundColor(Color.TRANSPARENT);
 
         // TODO(b/286551975): Replace this content with the touchpad debug view.
-
         TextView textView1 = new TextView(context);
         textView1.setBackgroundColor(Color.parseColor("#FFFF0000"));
         textView1.setTextSize(20);
         textView1.setText("Touchpad Debug View 1");
         textView1.setGravity(Gravity.CENTER);
         textView1.setTextColor(Color.WHITE);
-
         textView1.setLayoutParams(new LayoutParams(1000, 200));
 
         TextView textView2 = new TextView(context);
@@ -63,9 +107,98 @@
 
         addView(textView1);
         addView(textView2);
+
+        updateScreenDimensions();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        float deltaX;
+        float deltaY;
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mWindowLocationBeforeDragX = mWindowLayoutParams.x;
+                mWindowLocationBeforeDragY = mWindowLayoutParams.y;
+                mTouchDownX = event.getRawX() - mWindowLocationBeforeDragX;
+                mTouchDownY = event.getRawY() - mWindowLocationBeforeDragY;
+                return true;
+
+            case MotionEvent.ACTION_MOVE:
+                deltaX = event.getRawX() - mWindowLayoutParams.x - mTouchDownX;
+                deltaY = event.getRawY() - mWindowLayoutParams.y - mTouchDownY;
+                Slog.d("TouchpadDebugView", "Slop = " + mTouchSlop);
+                if (isSlopExceeded(deltaX, deltaY)) {
+                    Slog.d("TouchpadDebugView", "Slop exceeded");
+                    mWindowLayoutParams.x =
+                            Math.max(0, Math.min((int) (event.getRawX() - mTouchDownX),
+                                    mScreenWidth - this.getWidth()));
+                    mWindowLayoutParams.y =
+                            Math.max(0, Math.min((int) (event.getRawY() - mTouchDownY),
+                                    mScreenHeight - this.getHeight()));
+
+                    Slog.d("TouchpadDebugView", "New position X: "
+                            + mWindowLayoutParams.x + ", Y: " + mWindowLayoutParams.y);
+
+                    mWindowManager.updateViewLayout(this, mWindowLayoutParams);
+                }
+                return true;
+
+            case MotionEvent.ACTION_UP:
+                deltaX = event.getRawX() - mWindowLayoutParams.x - mTouchDownX;
+                deltaY = event.getRawY() - mWindowLayoutParams.y - mTouchDownY;
+                if (!isSlopExceeded(deltaX, deltaY)) {
+                    performClick();
+                }
+                return true;
+
+            case MotionEvent.ACTION_CANCEL:
+                // Move the window back to the original position
+                mWindowLayoutParams.x = mWindowLocationBeforeDragX;
+                mWindowLayoutParams.y = mWindowLocationBeforeDragY;
+                mWindowManager.updateViewLayout(this, mWindowLayoutParams);
+                return true;
+
+            default:
+                return super.onTouchEvent(event);
+        }
+    }
+
+    @Override
+    public boolean performClick() {
+        super.performClick();
+        Slog.d("TouchpadDebugView", "You clicked me!");
+        return true;
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateScreenDimensions();
+
+        // Adjust view position to stay within screen bounds after rotation
+        mWindowLayoutParams.x =
+                Math.max(0, Math.min(mWindowLayoutParams.x, mScreenWidth - getWidth()));
+        mWindowLayoutParams.y =
+                Math.max(0, Math.min(mWindowLayoutParams.y, mScreenHeight - getHeight()));
+        mWindowManager.updateViewLayout(this, mWindowLayoutParams);
+    }
+
+    private boolean isSlopExceeded(float deltaX, float deltaY) {
+        return deltaX * deltaX + deltaY * deltaY >= mTouchSlop * mTouchSlop;
+    }
+
+    private void updateScreenDimensions() {
+        Rect windowBounds =
+                mWindowManager.getCurrentWindowMetrics().getBounds();
+        mScreenWidth = windowBounds.width();
+        mScreenHeight = windowBounds.height();
     }
 
     public int getTouchpadId() {
         return mTouchpadId;
     }
+
+    public WindowManager.LayoutParams getWindowLayoutParams() {
+        return mWindowLayoutParams;
+    }
 }
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
index c7760c6..c28e74a 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
@@ -18,14 +18,10 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.graphics.PixelFormat;
-import android.hardware.display.DisplayManager;
 import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Slog;
-import android.view.Display;
-import android.view.Gravity;
 import android.view.InputDevice;
 import android.view.WindowManager;
 
@@ -34,58 +30,72 @@
 
 import java.util.Objects;
 
-public class TouchpadDebugViewController {
+public class TouchpadDebugViewController implements InputManager.InputDeviceListener {
 
-    private static final String TAG = "TouchpadDebugViewController";
+    private static final String TAG = "TouchpadDebugView";
 
     private final Context mContext;
     private final Handler mHandler;
+
     @Nullable
     private TouchpadDebugView mTouchpadDebugView;
+
     private final InputManagerService mInputManagerService;
+    private boolean mTouchpadVisualizerEnabled = false;
 
     public TouchpadDebugViewController(Context context, Looper looper,
-                                       InputManagerService inputManagerService) {
-        final DisplayManager displayManager = Objects.requireNonNull(
-                context.getSystemService(DisplayManager.class));
-        final Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
-        mContext = context.createDisplayContext(defaultDisplay);
+            InputManagerService inputManagerService) {
+        //TODO(b/363979581): Handle multi-display scenarios
+        mContext = context;
         mHandler = new Handler(looper);
         mInputManagerService = inputManagerService;
     }
 
-    public void systemRunning() {
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
         final InputManager inputManager = Objects.requireNonNull(
                 mContext.getSystemService(InputManager.class));
-        inputManager.registerInputDeviceListener(mInputDeviceListener, mHandler);
-        for (int deviceId : inputManager.getInputDeviceIds()) {
-            mInputDeviceListener.onInputDeviceAdded(deviceId);
+        InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+
+        if (Objects.requireNonNull(inputDevice).supportsSource(
+                InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)
+                && mTouchpadVisualizerEnabled) {
+            showDebugView(deviceId);
         }
     }
 
-    private final InputManager.InputDeviceListener mInputDeviceListener =
-            new InputManager.InputDeviceListener() {
-                @Override
-                public void onInputDeviceAdded(int deviceId) {
-                    final InputManager inputManager = Objects.requireNonNull(
-                            mContext.getSystemService(InputManager.class));
-                    InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        hideDebugView(deviceId);
+    }
 
-                    if (Objects.requireNonNull(inputDevice).supportsSource(
-                            InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)) {
-                        showDebugView(deviceId);
-                    }
-                }
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+    }
 
-                @Override
-                public void onInputDeviceRemoved(int deviceId) {
-                    hideDebugView(deviceId);
-                }
-
-                @Override
-                public void onInputDeviceChanged(int deviceId) {
-                }
-            };
+    /**
+     * Notify the controller that the touchpad visualizer setting value has changed.
+     * This must be called from the same looper thread as {@code mHandler}.
+     */
+    public void updateTouchpadVisualizerEnabled(boolean touchpadVisualizerEnabled) {
+        if (mTouchpadVisualizerEnabled == touchpadVisualizerEnabled) {
+            return;
+        }
+        mTouchpadVisualizerEnabled = touchpadVisualizerEnabled;
+        final InputManager inputManager = Objects.requireNonNull(
+                mContext.getSystemService(InputManager.class));
+        if (touchpadVisualizerEnabled) {
+            inputManager.registerInputDeviceListener(this, mHandler);
+            for (int deviceId : inputManager.getInputDeviceIds()) {
+                onInputDeviceAdded(deviceId);
+            }
+        } else {
+            if (mTouchpadDebugView != null) {
+                hideDebugView(mTouchpadDebugView.getTouchpadId());
+            }
+            inputManager.unregisterInputDeviceListener(this);
+        }
+    }
 
     private void showDebugView(int touchpadId) {
         if (mTouchpadDebugView != null) {
@@ -95,32 +105,15 @@
                 mContext.getSystemService(WindowManager.class));
 
         mTouchpadDebugView = new TouchpadDebugView(mContext, touchpadId);
+        final WindowManager.LayoutParams mWindowLayoutParams =
+                mTouchpadDebugView.getWindowLayoutParams();
 
-        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
-        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-        lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-        lp.setFitInsetsTypes(0);
-        lp.layoutInDisplayCutoutMode =
-                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        lp.format = PixelFormat.TRANSLUCENT;
-        lp.setTitle("TouchpadDebugView - display " + mContext.getDisplayId());
-        lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
-
-        lp.x = 40;
-        lp.y = 100;
-        lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
-        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
-        lp.gravity = Gravity.TOP | Gravity.LEFT;
-
-        wm.addView(mTouchpadDebugView, lp);
+        wm.addView(mTouchpadDebugView, mWindowLayoutParams);
         Slog.d(TAG, "Touchpad debug view created.");
 
         TouchpadHardwareProperties mTouchpadHardwareProperties =
                 mInputManagerService.getTouchpadHardwareProperties(
                         touchpadId);
-        // TODO(b/360137366): Use the hardware properties to initialise layout parameters.
         if (mTouchpadHardwareProperties != null) {
             Slog.d(TAG, mTouchpadHardwareProperties.toString());
         } else {
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
index 58e3452..e1f26d6 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -116,11 +116,11 @@
         boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
                 @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
                 @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
-                @SoftInputShowHideReason int reason);
+                @SoftInputShowHideReason int reason, boolean async);
 
         boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
                 @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
-                ResultReceiver resultReceiver, @SoftInputShowHideReason int reason);
+                ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async);
 
         @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
         void hideSoftInputFromServerForTest();
@@ -132,7 +132,8 @@
                 @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
                 IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
                 int unverifiedTargetSdkVersion, @UserIdInt int userId,
-                @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq);
+                @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
+                boolean useAsyncShowHideMethod);
 
         InputBindResult startInputOrWindowGainedFocus(
                 @StartInputReason int startInputReason, IInputMethodClient client,
@@ -290,17 +291,17 @@
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
             @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
             @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+            @SoftInputShowHideReason int reason, boolean async) {
         return mCallback.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
-                resultReceiver, reason);
+                resultReceiver, reason, async);
     }
 
     @Override
     public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
             @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async) {
         return mCallback.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
-                reason);
+                reason, async);
     }
 
     @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
@@ -336,11 +337,13 @@
             IRemoteInputConnection inputConnection,
             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             int unverifiedTargetSdkVersion, @UserIdInt int userId,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
+            boolean useAsyncShowHideMethod) {
         mCallback.startInputOrWindowGainedFocusAsync(
                 startInputReason, client, windowToken, startInputFlags, softInputMode,
                 windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
-                unverifiedTargetSdkVersion, userId, imeDispatcher, startInputSeq);
+                unverifiedTargetSdkVersion, userId, imeDispatcher, startInputSeq,
+                useAsyncShowHideMethod);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f34b4e9..af0ccf9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -326,7 +326,7 @@
      * Figures out the target IME user ID for a given {@link Binder} IPC.
      *
      * @param callingProcessUserId the user ID of the calling process
-     * @return User ID to be used for this {@link Binder} call.
+     * @return the user ID to be used for this {@link Binder} call
      */
     @GuardedBy("ImfLock.class")
     @UserIdInt
@@ -336,6 +336,32 @@
     }
 
     /**
+     * Figures out the targetIMuser for a given {@link Binder} IPC. In case
+     * {@code callingProcessUserId} is SYSTEM user, then it will return the owner of the display
+     * associated with the {@code client} passed as parameter.
+     *
+     * @param callingProcessUserId the user ID of the calling process
+     * @param client               the input method client used to retrieve the user id in case
+     *                             {@code callingProcessUserId} is assigned to SYSTEM user
+     * @return the user ID to be used for this {@link Binder} call
+     */
+    @GuardedBy("ImfLock.class")
+    @UserIdInt
+    @BinderThread
+    private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId,
+            @NonNull IInputMethodClient client) {
+        if (mConcurrentMultiUserModeEnabled) {
+            if (callingProcessUserId == UserHandle.USER_SYSTEM) {
+                final var clientState = mClientController.getClient(client.asBinder());
+                return mUserManagerInternal.getUserAssignedToDisplay(
+                        clientState.mSelfReportedDisplayId);
+            }
+            return callingProcessUserId;
+        }
+        return mCurrentImeUserId;
+    }
+
+   /**
      * Figures out the target IME user ID associated with the given {@code displayId}.
      *
      * @param displayId the display ID to be queried about
@@ -3037,6 +3063,7 @@
                 intent.putExtra("input_method_id", id);
                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
             }
+            bindingController.unbindCurrentMethod();
             unbindCurrentClientLocked(UnbindReason.SWITCH_IME, userId);
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -3061,14 +3088,14 @@
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
             @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
             int lastClickToolType, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
+            @SoftInputShowHideReason int reason, boolean async) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#showSoftInput", mDumper);
         synchronized (ImfLock.class) {
             final int uid = Binder.getCallingUid();
             final int callingUserId = UserHandle.getUserId(uid);
-            final int userId = resolveImeUserIdLocked(callingUserId);
+            final int userId = resolveImeUserIdLocked(callingUserId, client);
             final boolean result = showSoftInputLocked(client, windowToken, statsToken, flags,
                     lastClickToolType, resultReceiver, reason, uid, userId);
             // When ZeroJankProxy is enabled, the app has already received "true" as the return
@@ -3508,13 +3535,13 @@
     @Override
     public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
             @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async) {
         ImeTracing.getInstance().triggerManagerServiceDump(
                 "InputMethodManagerService#hideSoftInput", mDumper);
         synchronized (ImfLock.class) {
             final int uid = Binder.getCallingUid();
             final int callingUserId = UserHandle.getUserId(uid);
-            final int userId = resolveImeUserIdLocked(callingUserId);
+            final int userId = resolveImeUserIdLocked(callingUserId, client);
             final boolean result = hideSoftInputLocked(client, windowToken, statsToken, flags,
                     resultReceiver, reason, uid, userId);
             // When ZeroJankProxy is enabled, the app has already received "true" as the return
@@ -3650,7 +3677,8 @@
             IRemoteInputConnection inputConnection,
             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             int unverifiedTargetSdkVersion, @UserIdInt int userId,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
+            boolean useAsyncShowHideMethod) {
         // implemented by ZeroJankProxy
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index d9e9e00..cf2cdc1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -33,6 +33,7 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Printer;
 import android.util.Slog;
@@ -115,7 +116,11 @@
         final var selectedImi = selectedIndex >= 0 ? items.get(selectedIndex).mImi : null;
         final var languageSettingsIntent = selectedImi != null
                 ? selectedImi.createImeLanguageSettingsActivityIntent() : null;
-        final boolean hasLanguageSettingsButton = languageSettingsIntent != null;
+        final boolean isDeviceProvisioned = Settings.Global.getInt(
+                dialogWindowContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+                0) != 0;
+        final boolean hasLanguageSettingsButton = languageSettingsIntent != null
+                && isDeviceProvisioned;
         if (hasLanguageSettingsButton) {
             final View buttonBar = contentView
                     .requireViewById(com.android.internal.R.id.button_bar);
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index c940a9c..214aa1d 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -77,6 +77,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A proxy that processes all {@link IInputMethodManager} calls asynchronously.
@@ -175,19 +176,45 @@
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
             @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
             @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
-        offload(() -> mInner.showSoftInput(
-                client, windowToken, statsToken, flags, lastClickToolType, resultReceiver, reason));
-        return true;
+            @SoftInputShowHideReason int reason, boolean async) {
+
+        if (async) {
+            offload(() -> mInner.showSoftInput(
+                    client, windowToken, statsToken, flags, lastClickToolType, resultReceiver,
+                    reason, async));
+            return true;
+        } else {
+            final var future = CompletableFuture.supplyAsync(
+                    () -> mInner.showSoftInput(
+                            client,
+                            windowToken,
+                            statsToken,
+                            flags,
+                            lastClickToolType,
+                            resultReceiver,
+                            reason,
+                            async),
+                    this::offload);
+            return future.completeOnTimeout(false, 1, TimeUnit.SECONDS).join();
+        }
     }
 
     @Override
     public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
             @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
-        offload(() -> mInner.hideSoftInput(
-                client, windowToken, statsToken, flags, resultReceiver, reason));
-        return true;
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async) {
+
+        if (async) {
+            offload(() -> mInner.hideSoftInput(
+                    client, windowToken, statsToken, flags, resultReceiver, reason, async));
+            return true;
+        } else {
+            final var future = CompletableFuture.supplyAsync(
+                    () -> mInner.hideSoftInput(
+                            client, windowToken, statsToken, flags, resultReceiver, reason, async),
+                    this::offload);
+            return future.completeOnTimeout(false, 1, TimeUnit.SECONDS).join();
+        }
     }
 
     @Override
@@ -207,7 +234,8 @@
             IRemoteInputConnection inputConnection,
             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             int unverifiedTargetSdkVersion, @UserIdInt int userId,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq,
+            boolean useAsyncShowHideMethod) {
         offload(() -> {
             InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client,
                     windowToken, startInputFlags, softInputMode, windowFlags,
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/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index c8cb54f..bad959a 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -19,6 +19,9 @@
 import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
 import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
 
+import static com.android.server.notification.ZenLog.traceApplyDeviceEffect;
+import static com.android.server.notification.ZenLog.traceScheduleApplyDeviceEffect;
+
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
 import android.content.BroadcastReceiver;
@@ -77,6 +80,8 @@
             if (mLastAppliedEffects.shouldSuppressAmbientDisplay()
                     != effects.shouldSuppressAmbientDisplay()) {
                 try {
+                    traceApplyDeviceEffect("suppressAmbientDisplay",
+                            effects.shouldSuppressAmbientDisplay());
                     mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
                             effects.shouldSuppressAmbientDisplay());
                 } catch (Exception e) {
@@ -87,6 +92,8 @@
             if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) {
                 if (mColorDisplayManager != null) {
                     try {
+                        traceApplyDeviceEffect("displayGrayscale",
+                                effects.shouldDisplayGrayscale());
                         mColorDisplayManager.setSaturationLevel(
                                 effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
                                         : SATURATION_LEVEL_FULL_COLOR);
@@ -99,6 +106,7 @@
             if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) {
                 if (mWallpaperManager != null) {
                     try {
+                        traceApplyDeviceEffect("dimWallpaper", effects.shouldDimWallpaper());
                         mWallpaperManager.setWallpaperDimAmount(
                                 effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
                                         : WALLPAPER_DIM_AMOUNT_NORMAL);
@@ -134,6 +142,7 @@
             unregisterScreenOffReceiver();
             updateNightModeImmediately(useNightMode);
         } else {
+            traceScheduleApplyDeviceEffect("nightMode", useNightMode);
             registerScreenOffReceiver();
         }
     }
@@ -150,6 +159,7 @@
     private void updateNightModeImmediately(boolean useNightMode) {
         Binder.withCleanCallingIdentity(() -> {
             try {
+                traceApplyDeviceEffect("nightMode", useNightMode);
                 mUiModeManager.setAttentionModeThemeOverlay(
                         useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT
                                 : MODE_ATTENTION_THEME_OVERLAY_OFF);
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 008746c..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,
@@ -830,61 +849,19 @@
                 }
             }
 
+            // The list of notification operations required after the channel update
             final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
 
-            final Set<FullyQualifiedGroupKey> oldGroups =
-                    new HashSet<>(mAggregatedNotifications.keySet());
-            for (FullyQualifiedGroupKey oldFullAggKey : oldGroups) {
-                // Only check aggregate groups that match the same userId & packageName
-                if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
-                    final ArrayMap<String, NotificationAttributes> notificationsInAggGroup =
-                            mAggregatedNotifications.get(oldFullAggKey);
-                    if (notificationsInAggGroup == null) {
-                        continue;
-                    }
+            // Check any already auto-grouped notifications that may need to be re-grouped
+            // after the channel update
+            notificationsToMove.addAll(
+                    getAutogroupedNotificationsMoveOps(userId, pkgName,
+                        notificationsToCheck));
 
-                    FullyQualifiedGroupKey newFullAggregateGroupKey = null;
-                    for (String key : notificationsInAggGroup.keySet()) {
-                        if (notificationsToCheck.get(key) != null) {
-                            // check if section changes
-                            NotificationSectioner sectioner = getSection(
-                                    notificationsToCheck.get(key));
-                            if (sectioner == null) {
-                                continue;
-                            }
-                            newFullAggregateGroupKey = new FullyQualifiedGroupKey(userId, pkgName,
-                                    sectioner);
-                            if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
-                                if (DEBUG) {
-                                    Log.i(TAG, "Change section on channel update: " + key);
-                                }
-                                notificationsToMove.add(
-                                        new NotificationMoveOp(notificationsToCheck.get(key),
-                                            oldFullAggKey, newFullAggregateGroupKey));
-                            }
-                        }
-                    }
-
-                    if (newFullAggregateGroupKey != null) {
-                        // Add any notifications left ungrouped to the new section
-                        ArrayMap<String, NotificationAttributes> ungrouped =
-                            mUngroupedAbuseNotifications.get(newFullAggregateGroupKey);
-                        if (ungrouped != null) {
-                            for (NotificationRecord r : notificationList) {
-                                if (ungrouped.containsKey(r.getKey())) {
-                                    if (DEBUG) {
-                                        Log.i(TAG, "Add previously ungrouped: " + r);
-                                    }
-                                    notificationsToMove.add(
-                                        new NotificationMoveOp(r, null, newFullAggregateGroupKey));
-                                }
-                            }
-                            //Cleanup mUngroupedAbuseNotifications
-                            mUngroupedAbuseNotifications.remove(newFullAggregateGroupKey);
-                        }
-                    }
-                }
-            }
+            // Check any ungrouped notifications that may need to be auto-grouped
+            // after the channel update
+            notificationsToMove.addAll(
+                    getUngroupedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
 
             // Batch move to new section
             if (!notificationsToMove.isEmpty()) {
@@ -894,10 +871,103 @@
     }
 
     @GuardedBy("mAggregatedNotifications")
+    private List<NotificationMoveOp> getAutogroupedNotificationsMoveOps(int userId, String pkgName,
+            ArrayMap<String, NotificationRecord> notificationsToCheck) {
+        final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+        final Set<FullyQualifiedGroupKey> oldGroups =
+                new HashSet<>(mAggregatedNotifications.keySet());
+        // Move auto-grouped updated notifications from the old groups to the new groups (section)
+        for (FullyQualifiedGroupKey oldFullAggKey : oldGroups) {
+            // Only check aggregate groups that match the same userId & packageName
+            if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
+                final ArrayMap<String, NotificationAttributes> notificationsInAggGroup =
+                        mAggregatedNotifications.get(oldFullAggKey);
+                if (notificationsInAggGroup == null) {
+                    continue;
+                }
+
+                FullyQualifiedGroupKey newFullAggregateGroupKey = null;
+                for (String key : notificationsInAggGroup.keySet()) {
+                    if (notificationsToCheck.get(key) != null) {
+                        // check if section changes
+                        NotificationSectioner sectioner = getSection(notificationsToCheck.get(key));
+                        if (sectioner == null) {
+                            continue;
+                        }
+                        newFullAggregateGroupKey = new FullyQualifiedGroupKey(userId, pkgName,
+                                sectioner);
+                        if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
+                            if (DEBUG) {
+                                Log.i(TAG, "Change section on channel update: " + key);
+                            }
+                            notificationsToMove.add(
+                                    new NotificationMoveOp(notificationsToCheck.get(key),
+                                        oldFullAggKey, newFullAggregateGroupKey));
+                            notificationsToCheck.remove(key);
+                        }
+                    }
+                }
+            }
+        }
+        return notificationsToMove;
+    }
+
+    @GuardedBy("mAggregatedNotifications")
+    private List<NotificationMoveOp> getUngroupedNotificationsMoveOps(int userId, String pkgName,
+            final ArrayMap<String, NotificationRecord> notificationsToCheck) {
+        final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
+        // Move any remaining ungrouped updated notifications from the old ungrouped list
+        // to the new ungrouped section list, if necessary
+        if (!notificationsToCheck.isEmpty()) {
+            final Set<FullyQualifiedGroupKey> oldUngroupedSectionKeys =
+                    new HashSet<>(mUngroupedAbuseNotifications.keySet());
+            for (FullyQualifiedGroupKey oldFullAggKey : oldUngroupedSectionKeys) {
+                // Only check aggregate groups that match the same userId & packageName
+                if (pkgName.equals(oldFullAggKey.pkg) && userId == oldFullAggKey.userId) {
+                    final ArrayMap<String, NotificationAttributes> ungroupedOld =
+                            mUngroupedAbuseNotifications.get(oldFullAggKey);
+                    if (ungroupedOld == null) {
+                        continue;
+                    }
+
+                    FullyQualifiedGroupKey newFullAggregateGroupKey = null;
+                    final Set<String> ungroupedKeys = new HashSet<>(ungroupedOld.keySet());
+                    for (String key : ungroupedKeys) {
+                        NotificationRecord record = notificationsToCheck.get(key);
+                        if (record != null) {
+                            // check if section changes
+                            NotificationSectioner sectioner = getSection(record);
+                            if (sectioner == null) {
+                                continue;
+                            }
+                            newFullAggregateGroupKey = new FullyQualifiedGroupKey(userId, pkgName,
+                                    sectioner);
+                            if (!oldFullAggKey.equals(newFullAggregateGroupKey)) {
+                                if (DEBUG) {
+                                    Log.i(TAG, "Change ungrouped section: " + key);
+                                }
+                                notificationsToMove.add(
+                                        new NotificationMoveOp(record, oldFullAggKey,
+                                            newFullAggregateGroupKey));
+                                notificationsToCheck.remove(key);
+                                //Remove from previous ungrouped list
+                                ungroupedOld.remove(key);
+                            }
+                        }
+                    }
+                    mUngroupedAbuseNotifications.put(oldFullAggKey, ungroupedOld);
+                }
+            }
+        }
+        return notificationsToMove;
+    }
+
+    @GuardedBy("mAggregatedNotifications")
     private void moveNotificationsToNewSection(final int userId, final String pkgName,
             final List<NotificationMoveOp> notificationsToMove) {
         record GroupUpdateOp(FullyQualifiedGroupKey groupKey, NotificationRecord record,
                              boolean hasSummary) { }
+        // Bundled operations to apply to groups affected by the channel update
         ArrayMap<FullyQualifiedGroupKey, GroupUpdateOp> groupsToUpdate = new ArrayMap<>();
 
         for (NotificationMoveOp moveOp: notificationsToMove) {
@@ -923,35 +993,36 @@
                 // Only add once, for triggering notification
                 if (!groupsToUpdate.containsKey(oldFullAggregateGroupKey)) {
                     groupsToUpdate.put(oldFullAggregateGroupKey,
-                            new GroupUpdateOp(oldFullAggregateGroupKey, record, true));
+                        new GroupUpdateOp(oldFullAggregateGroupKey, record, true));
                 }
             }
 
-            // Add/update aggregate summary for new group
+            // Add moved notifications to the ungrouped list for new group and do grouping
+            // after all notifications have been handled
             if (newFullAggregateGroupKey != null) {
                 final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
                         mAggregatedNotifications.getOrDefault(newFullAggregateGroupKey,
                             new ArrayMap<>());
-                boolean newGroupExists = !newAggregatedNotificationsAttrs.isEmpty();
-                newAggregatedNotificationsAttrs.put(record.getKey(),
-                        new NotificationAttributes(record.getFlags(),
-                            record.getNotification().getSmallIcon(),
-                            record.getNotification().color,
-                            record.getNotification().visibility,
-                            record.getNotification().getGroupAlertBehavior(),
-                            record.getChannel().getId()));
-                mAggregatedNotifications.put(newFullAggregateGroupKey,
-                        newAggregatedNotificationsAttrs);
+                boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
+                ArrayMap<String, NotificationAttributes> ungrouped =
+                        mUngroupedAbuseNotifications.getOrDefault(newFullAggregateGroupKey,
+                            new ArrayMap<>());
+                ungrouped.put(record.getKey(), new NotificationAttributes(
+                        record.getFlags(),
+                        record.getNotification().getSmallIcon(),
+                        record.getNotification().color,
+                        record.getNotification().visibility,
+                        record.getNotification().getGroupAlertBehavior(),
+                        record.getChannel().getId()));
+                mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);
+
+                record.setOverrideGroupKey(null);
 
                 // Only add once, for triggering notification
                 if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
                     groupsToUpdate.put(newFullAggregateGroupKey,
-                            new GroupUpdateOp(newFullAggregateGroupKey, record, newGroupExists));
+                        new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
                 }
-
-                // Add notification to new group. do not request resort
-                record.setOverrideGroupKey(null);
-                mCallback.addAutoGroup(record.getKey(), newFullAggregateGroupKey.toString(), false);
             }
         }
 
@@ -959,18 +1030,26 @@
         for (FullyQualifiedGroupKey groupKey : groupsToUpdate.keySet()) {
             final ArrayMap<String, NotificationAttributes> aggregatedNotificationsAttrs =
                     mAggregatedNotifications.getOrDefault(groupKey, new ArrayMap<>());
-            if (aggregatedNotificationsAttrs.isEmpty()) {
-                mCallback.removeAutoGroupSummary(userId, pkgName, groupKey.toString());
-                mAggregatedNotifications.remove(groupKey);
-            } else {
-                NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
-                boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
+            final ArrayMap<String, NotificationAttributes> ungrouped =
+                    mUngroupedAbuseNotifications.getOrDefault(groupKey, new ArrayMap<>());
+
+            NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
+            boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
+            //Group needs to be created/updated
+            if (ungrouped.size() >= mAutoGroupAtCount
+                    || (hasSummary && !aggregatedNotificationsAttrs.isEmpty())) {
                 NotificationSectioner sectioner = getSection(triggeringNotification);
                 if (sectioner == null) {
                     continue;
                 }
-                updateAggregateAppGroup(groupKey, triggeringNotification.getKey(), hasSummary,
-                        sectioner.mSummaryId);
+                aggregateUngroupedNotifications(groupKey, triggeringNotification.getKey(),
+                        ungrouped, hasSummary, sectioner.mSummaryId);
+            } else {
+                // Remove empty groups
+                if (aggregatedNotificationsAttrs.isEmpty() && hasSummary) {
+                    mCallback.removeAutoGroupSummary(userId, pkgName, groupKey.toString());
+                    mAggregatedNotifications.remove(groupKey);
+                }
             }
         }
     }
@@ -1327,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..54e9189 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)
@@ -7595,16 +7600,14 @@
                     + " trying to post for invalid pkg " + pkg + " in user " + incomingUserId);
         }
 
-        if (android.app.Flags.secureAllowlistToken()) {
-            IBinder allowlistToken = notification.getAllowlistToken();
-            if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) {
-                throw new SecurityException(
-                        "Unexpected allowlist token received from " + callingUid);
-            }
-            // allowlistToken is populated by unparceling, so it can be null if the notification was
-            // posted from inside system_server. Ensure it's the expected value.
-            notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
+        IBinder allowlistToken = notification.getAllowlistToken();
+        if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) {
+            throw new SecurityException(
+                    "Unexpected allowlist token received from " + callingUid);
         }
+        // allowlistToken is populated by unparceling, so it can be null if the notification was
+        // posted from inside system_server. Ensure it's the expected value.
+        notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
 
         checkRestrictedCategories(notification);
 
@@ -8769,12 +8772,10 @@
          */
         private boolean enqueueNotification() {
             synchronized (mNotificationLock) {
-                if (android.app.Flags.secureAllowlistToken()) {
-                    // allowlistToken is populated by unparceling, so it will be absent if the
-                    // EnqueueNotificationRunnable is created directly by NMS (as we do for group
-                    // summaries) instead of via notify(). Fix that.
-                    r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN);
-                }
+                // allowlistToken is populated by unparceling, so it will be absent if the
+                // EnqueueNotificationRunnable is created directly by NMS (as we do for group
+                // summaries) instead of via notify(). Fix that.
+                r.getNotification().overrideAllowlistToken(ALLOWLIST_TOKEN);
 
                 final long snoozeAt =
                         mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
@@ -11741,17 +11742,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 +13374,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 +13402,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 +13415,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 +13426,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 +13442,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/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 82c5733..1aa5ac0 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -23,18 +23,15 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.provider.Settings.Global;
-import android.service.notification.Condition;
 import android.service.notification.IConditionProvider;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeDiff;
 import android.util.LocalLog;
-import android.util.Log;
-import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
 import java.util.List;
 
 public class ZenLog {
@@ -61,6 +58,8 @@
     private static final int TYPE_RECORD_CALLER = 19;
     private static final int TYPE_CHECK_REPEAT_CALLER = 20;
     private static final int TYPE_ALERT_ON_UPDATED_INTERCEPT = 21;
+    private static final int TYPE_APPLY_DEVICE_EFFECT = 22;
+    private static final int TYPE_SCHEDULE_APPLY_DEVICE_EFFECT = 23;
 
     public static void traceIntercepted(NotificationRecord record, String reason) {
         append(TYPE_INTERCEPTED, record.getKey() + "," + reason);
@@ -173,6 +172,14 @@
                 + ", given uri=" + hasUri);
     }
 
+    public static void traceApplyDeviceEffect(String effect, boolean newValue) {
+        append(TYPE_APPLY_DEVICE_EFFECT, effect + " -> " + newValue);
+    }
+
+    public static void traceScheduleApplyDeviceEffect(String effect, boolean scheduledValue) {
+        append(TYPE_SCHEDULE_APPLY_DEVICE_EFFECT, effect + " -> " + scheduledValue);
+    }
+
     private static String subscribeResult(IConditionProvider provider, RemoteException e) {
         return provider == null ? "no provider" : e != null ? e.getMessage() : "ok";
     }
@@ -196,6 +203,8 @@
             case TYPE_RECORD_CALLER: return "record_caller";
             case TYPE_CHECK_REPEAT_CALLER: return "check_repeat_caller";
             case TYPE_ALERT_ON_UPDATED_INTERCEPT: return "alert_on_updated_intercept";
+            case TYPE_APPLY_DEVICE_EFFECT: return "apply_device_effect";
+            case TYPE_SCHEDULE_APPLY_DEVICE_EFFECT: return "schedule_device_effect";
             default: return "unknown";
         }
     }
@@ -273,4 +282,14 @@
             STATE_CHANGES.dump(prefix, pw);
         }
     }
+
+    @VisibleForTesting(/* otherwise = VisibleForTesting.NONE */)
+    public static void clear() {
+        synchronized (INTERCEPTION_EVENTS) {
+            INTERCEPTION_EVENTS.clear();
+        }
+        synchronized (STATE_CHANGES) {
+            STATE_CHANGES.clear();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index ee3f48d..2ada9ae4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -226,10 +226,8 @@
         mDefaultConfig = Flags.modesUi()
                 ? ZenModeConfig.getDefaultConfig()
                 : readDefaultConfig(mContext.getResources());
-        updateDefaultConfigAutomaticRules();
-        if (Flags.modesApi()) {
-            updateDefaultAutomaticRulePolicies();
-        }
+        updateDefaultConfig(mContext, mDefaultConfig);
+
         mConfig = mDefaultConfig.copy();
         synchronized (mConfigsArrayLock) {
             mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
@@ -485,7 +483,7 @@
             newConfig = mConfig.copy();
             ZenRule rule = new ZenRule();
             populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true);
-            rule = maybeRestoreRemovedRule(newConfig, rule, automaticZenRule, origin);
+            rule = maybeRestoreRemovedRule(newConfig, pkg, rule, automaticZenRule, origin);
             newConfig.automaticRules.put(rule.id, rule);
             maybeReplaceDefaultRule(newConfig, automaticZenRule);
 
@@ -498,7 +496,7 @@
     }
 
     @GuardedBy("mConfigLock")
-    private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd,
+    private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, String pkg, ZenRule ruleToAdd,
             AutomaticZenRule azrToAdd, @ConfigOrigin int origin) {
         if (!Flags.modesApi()) {
             return ruleToAdd;
@@ -522,10 +520,18 @@
         if (origin != ORIGIN_APP) {
             return ruleToAdd; // Okay to create anew.
         }
+        if (Flags.modesUi()) {
+            if (!Objects.equals(ruleToRestore.pkg, pkg)
+                    || !Objects.equals(ruleToRestore.component, azrToAdd.getOwner())) {
+                // Apps are not allowed to change the owner via updateAutomaticZenRule(). Thus, if
+                // they have to, delete+add is their only option.
+                return ruleToAdd;
+            }
+        }
 
         // "Preserve" the previous rule by considering the azrToAdd an update instead.
         // Only app-modifiable fields will actually be modified.
-        populateZenRule(ruleToRestore.pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
+        populateZenRule(pkg, azrToAdd, ruleToRestore, origin, /* isNew= */ false);
         return ruleToRestore;
     }
 
@@ -757,7 +763,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");
@@ -1063,7 +1071,7 @@
     }
 
     void updateZenRulesOnLocaleChange() {
-        updateDefaultConfigAutomaticRules();
+        updateRuleStringsForCurrentLocale(mContext, mDefaultConfig);
         synchronized (mConfigLock) {
             if (mConfig == null) {
                 return;
@@ -1742,6 +1750,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;
+                        }
+                    }
                 }
             }
 
@@ -2108,17 +2125,25 @@
     @GuardedBy("mConfigLock")
     private void applyCustomPolicy(ZenPolicy policy, ZenRule rule, boolean useManualConfig) {
         if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
-            policy.apply(new ZenPolicy.Builder()
-                    .disallowAllSounds()
-                    .allowPriorityChannels(false)
-                    .build());
+            if (Flags.modesApi() && Flags.modesUi()) {
+                policy.apply(ZenPolicy.getBasePolicyInterruptionFilterNone());
+            } else {
+                policy.apply(new ZenPolicy.Builder()
+                        .disallowAllSounds()
+                        .allowPriorityChannels(false)
+                        .build());
+            }
         } else if (rule.zenMode == Global.ZEN_MODE_ALARMS) {
-            policy.apply(new ZenPolicy.Builder()
-                    .disallowAllSounds()
-                    .allowAlarms(true)
-                    .allowMedia(true)
-                    .allowPriorityChannels(false)
-                    .build());
+            if (Flags.modesApi() && Flags.modesUi()) {
+                policy.apply(ZenPolicy.getBasePolicyInterruptionFilterAlarms());
+            } else {
+                policy.apply(new ZenPolicy.Builder()
+                        .disallowAllSounds()
+                        .allowAlarms(true)
+                        .allowMedia(true)
+                        .allowPriorityChannels(false)
+                        .build());
+            }
         } else if (rule.zenPolicy != null) {
             policy.apply(rule.zenPolicy);
         } else {
@@ -2210,30 +2235,49 @@
         }
     }
 
-    private void updateDefaultConfigAutomaticRules() {
-        for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
+    /**
+     * Apply changes to the <em>default</em> {@link ZenModeConfig} so that the rules included by
+     * default (Events / Sleeping) support the latest Zen features and are ready for new users.
+     *
+     * <p>This includes: setting a fully populated ZenPolicy, setting correct type and
+     * allowManualInvocation=true, and ensuring default names and trigger descriptions correspond
+     * to the current locale.
+     */
+    private static void updateDefaultConfig(Context context, ZenModeConfig defaultConfig) {
+        if (Flags.modesApi()) {
+            updateDefaultAutomaticRulePolicies(defaultConfig);
+        }
+        if (Flags.modesApi() && Flags.modesUi()) {
+            SystemZenRules.maybeUpgradeRules(context, defaultConfig);
+        }
+        updateRuleStringsForCurrentLocale(context, defaultConfig);
+    }
+
+    private static void updateRuleStringsForCurrentLocale(Context context,
+            ZenModeConfig defaultConfig) {
+        for (ZenRule rule : defaultConfig.automaticRules.values()) {
             if (ZenModeConfig.EVENTS_DEFAULT_RULE_ID.equals(rule.id)) {
-                rule.name = mContext.getResources()
+                rule.name = context.getResources()
                         .getString(R.string.zen_mode_default_events_name);
             } else if (ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID.equals(rule.id)) {
-                rule.name = mContext.getResources()
+                rule.name = context.getResources()
                         .getString(R.string.zen_mode_default_every_night_name);
             }
             if (Flags.modesApi() && Flags.modesUi()) {
-                SystemZenRules.updateTriggerDescription(mContext, rule);
+                SystemZenRules.updateTriggerDescription(context, rule);
             }
         }
     }
 
     // Updates the policies in the default automatic rules (provided via default XML config) to
     // be fully filled in default values.
-    private void updateDefaultAutomaticRulePolicies() {
+    private static void updateDefaultAutomaticRulePolicies(ZenModeConfig defaultConfig) {
         if (!Flags.modesApi()) {
             // Should be checked before calling, but just in case.
             return;
         }
-        ZenPolicy defaultPolicy = mDefaultConfig.getZenPolicy();
-        for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
+        ZenPolicy defaultPolicy = defaultConfig.getZenPolicy();
+        for (ZenRule rule : defaultConfig.automaticRules.values()) {
             if (ZenModeConfig.DEFAULT_RULE_IDS.contains(rule.id) && rule.zenPolicy == null) {
                 rule.zenPolicy = defaultPolicy.copy();
             }
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/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index ee0159d..4665a72 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -3065,10 +3065,9 @@
             case DumpState.DUMP_PREFERRED_XML:
             {
                 pw.flush();
-                FileOutputStream fout = new FileOutputStream(fd);
-                BufferedOutputStream str = new BufferedOutputStream(fout);
                 TypedXmlSerializer serializer = Xml.newFastSerializer();
-                try {
+                try (BufferedOutputStream str =
+                             new BufferedOutputStream(new FileOutputStream(fd))) {
                     serializer.setOutput(str, StandardCharsets.UTF_8.name());
                     serializer.startDocument(null, true);
                     serializer.setFeature(
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5105fd3..662c792 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -209,6 +209,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 
+
 final class InstallPackageHelper {
     private final PackageManagerService mPm;
     private final AppDataHelper mAppDataHelper;
@@ -989,15 +990,6 @@
         return false;
     }
 
-    void installPackagesTraced(List<InstallRequest> requests) {
-        try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
-            installPackagesLI(requests);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
     /**
      * Installs one or more packages atomically. This operation is broken up into four phases:
      * <ul>
@@ -1017,15 +1009,31 @@
      *
      * Failure at any phase will result in a full failure to install all packages.
      */
-    @GuardedBy("mPm.mInstallLock")
-    private void installPackagesLI(List<InstallRequest> requests) {
-        final Set<String> scannedPackages = new ArraySet<>(requests.size());
-        final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
-        final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
-        CriticalEventLog.getInstance().logInstallPackagesStarted();
+    void installPackagesTraced(List<InstallRequest> requests) {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
         boolean success = false;
+        final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
+        final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
         try {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
+            CriticalEventLog.getInstance().logInstallPackagesStarted();
+
+            if (prepareInstallPackages(requests)
+                    && scanInstallPackages(requests, createdAppId, versionInfos)) {
+                List<ReconciledPackage> reconciledPackages =
+                        reconcileInstallPackages(requests, versionInfos);
+                if (reconciledPackages != null && commitInstallPackages(reconciledPackages)) {
+                    success = true;
+                }
+            }
+        } finally {
+            completeInstallProcess(requests, createdAppId, success);
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    private boolean prepareInstallPackages(List<InstallRequest> requests) {
+        // TODO: will remove the locking after doRename is moved out of prepare
+        try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
             for (InstallRequest request : requests) {
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
@@ -1036,17 +1044,27 @@
                             prepareFailure.getMessage());
                     request.setOriginPackage(prepareFailure.mConflictingPackage);
                     request.setOriginPermission(prepareFailure.mConflictingPermission);
-                    return;
+                    return false;
                 } finally {
                     request.onPrepareFinished();
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
+            }
+        }
+        return true;
+    }
 
+    private boolean scanInstallPackages(List<InstallRequest> requests,
+            Map<String, Boolean> createdAppId, Map<String, Settings.VersionInfo> versionInfos) {
+        // TODO(b/362840929): remove locker
+        try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+            final Set<String> scannedPackages = new ArraySet<>(requests.size());
+            for (InstallRequest request : requests) {
                 final ParsedPackage packageToScan = request.getParsedPackage();
                 if (packageToScan == null) {
                     request.setError(INSTALL_FAILED_SESSION_INVALID,
                             "Failed to obtain package to scan");
-                    return;
+                    return false;
                 }
                 request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
                 final String packageName = packageToScan.getPackageName();
@@ -1064,7 +1082,7 @@
                                 "Duplicate package "
                                         + packageName
                                         + " in multi-package install request.");
-                        return;
+                        return false;
                     }
                     if (!checkNoAppStorageIsConsistent(
                             request.getScanRequestOldPackage(), packageToScan)) {
@@ -1074,7 +1092,7 @@
                                 INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                 "Update attempted to change value of "
                                         + PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
-                        return;
+                        return false;
                     }
                     final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
                     final boolean isSdkLibrary = packageToScan.isSdkLibrary();
@@ -1087,7 +1105,7 @@
                             mPm.getSettingsVersionForPackage(packageToScan));
                 } catch (PackageManagerException e) {
                     request.setError("Scanning Failed.", e);
-                    return;
+                    return false;
                 }
                 if (request.isArchived()) {
                     final SparseArray<String> responsibleInstallerTitles =
@@ -1099,17 +1117,22 @@
                         request.setError(PackageManagerException.ofInternalError(
                                 "Failed to obtain the responsible installer info",
                                 INTERNAL_ERROR_ARCHIVE_NO_INSTALLER_TITLE));
-                        return;
+                        return false;
                     }
                     request.setResponsibleInstallerTitles(responsibleInstallerTitles);
                 }
             }
+        }
+        return true;
+    }
 
-            List<ReconciledPackage> reconciledPackages;
+    private List<ReconciledPackage> reconcileInstallPackages(List<InstallRequest> requests,
+            Map<String, Settings.VersionInfo> versionInfos) {
+        try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
             synchronized (mPm.mLock) {
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
-                    reconciledPackages = ReconcilePackageUtils.reconcilePackages(
+                    return ReconcilePackageUtils.reconcilePackages(
                             requests, Collections.unmodifiableMap(mPm.mPackages),
                             versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
                             mPm.mSettings, mPm.mInjector.getSystemConfig());
@@ -1117,70 +1140,80 @@
                     for (InstallRequest request : requests) {
                         request.setError("Reconciliation failed...", e);
                     }
-                    return;
+                    return null;
                 } finally {
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
-                if (Flags.improveInstallFreeze()) {
-                    // Postpone freezer until after reconcile
-                    for (ReconciledPackage reconciledPkg : reconciledPackages) {
-                        InstallRequest installRequest = reconciledPkg.mInstallRequest;
-                        String packageName = installRequest.getParsedPackage().getPackageName();
-                        PackageFreezer freezer = freezePackageForInstall(packageName,
-                                UserHandle.USER_ALL, installRequest.getInstallFlags(),
-                                "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED,
-                                installRequest);
-                        installRequest.setFreezer(freezer);
-                    }
+            }
+        }
+    }
+
+
+    private boolean commitInstallPackages(List<ReconciledPackage> reconciledPackages) {
+        try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+            if (Flags.improveInstallFreeze()) {
+                // Postpone freezer until after reconcile
+                for (ReconciledPackage reconciledPkg : reconciledPackages) {
+                    InstallRequest installRequest = reconciledPkg.mInstallRequest;
+                    String packageName = installRequest.getParsedPackage().getPackageName();
+                    PackageFreezer freezer = freezePackageForInstall(packageName,
+                            UserHandle.USER_ALL, installRequest.getInstallFlags(),
+                            "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED,
+                            installRequest);
+                    installRequest.setFreezer(freezer);
                 }
+            }
+            synchronized (mPm.mLock) {
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages");
                     commitPackagesLocked(reconciledPackages, mPm.mUserManager.getUserIds());
-                    success = true;
                 } finally {
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
             }
             executePostCommitStepsLIF(reconciledPackages);
-        } finally {
-            if (success) {
-                for (InstallRequest request : requests) {
-                    if (request.getDataLoaderType() != DataLoaderType.INCREMENTAL) {
-                        continue;
-                    }
-                    if (request.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) {
-                        continue;
-                    }
-                    // For incremental installs, we bypass the verifier prior to install. Now
-                    // that we know the package is valid, send a notice to the verifier with
-                    // the root hash of the base.apk.
-                    final String baseCodePath = request.getPkg().getBaseApkPath();
-                    final String[] splitCodePaths = request.getPkg().getSplitCodePaths();
-                    final Uri originUri = request.getOriginUri();
-                    final int verificationId = mPm.mPendingVerificationToken++;
-                    final String rootHashString = PackageManagerServiceUtils
-                            .buildVerificationRootHashString(baseCodePath, splitCodePaths);
-                    VerificationUtils.broadcastPackageVerified(verificationId, originUri,
-                            PackageManager.VERIFICATION_ALLOW, rootHashString,
-                            request.getDataLoaderType(), request.getUser(), mContext);
+        }
+        return true;
+    }
+
+    private void completeInstallProcess(List<InstallRequest> requests,
+            Map<String, Boolean> createdAppId, boolean success) {
+        if (success) {
+            for (InstallRequest request : requests) {
+                if (request.getDataLoaderType() != DataLoaderType.INCREMENTAL) {
+                    continue;
                 }
-            } else {
-                for (InstallRequest installRequest : requests) {
-                    if (installRequest.getParsedPackage() != null && createdAppId.getOrDefault(
-                            installRequest.getParsedPackage().getPackageName(), false)) {
-                        cleanUpAppIdCreation(installRequest);
-                    }
+                if (request.getSignatureSchemeVersion() != SIGNING_BLOCK_V4) {
+                    continue;
                 }
-                // TODO(b/194319951): create a more descriptive reason than unknown
-                // mark all non-failure installs as UNKNOWN so we do not treat them as success
-                for (InstallRequest request : requests) {
-                    request.closeFreezer();
-                    if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
-                        request.setReturnCode(PackageManager.INSTALL_UNKNOWN);
-                    }
+                // For incremental installs, we bypass the verifier prior to install. Now
+                // that we know the package is valid, send a notice to the verifier with
+                // the root hash of the base.apk.
+                final String baseCodePath = request.getPkg().getBaseApkPath();
+                final String[] splitCodePaths = request.getPkg().getSplitCodePaths();
+                final Uri originUri = request.getOriginUri();
+                final int verificationId = mPm.mPendingVerificationToken++;
+                final String rootHashString = PackageManagerServiceUtils
+                        .buildVerificationRootHashString(baseCodePath, splitCodePaths);
+                VerificationUtils.broadcastPackageVerified(verificationId, originUri,
+                        PackageManager.VERIFICATION_ALLOW, rootHashString,
+                        request.getDataLoaderType(), request.getUser(), mContext);
+            }
+        } else {
+            for (InstallRequest installRequest : requests) {
+                if (installRequest.getParsedPackage() != null && createdAppId.getOrDefault(
+                        installRequest.getParsedPackage().getPackageName(), false)) {
+                    cleanUpAppIdCreation(installRequest);
                 }
             }
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            // TODO(b/194319951): create a more descriptive reason than unknown
+            // mark all non-failure installs as UNKNOWN so we do not treat them as success
+            for (InstallRequest request : requests) {
+                request.closeFreezer();
+                if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
+                    request.setReturnCode(PackageManager.INSTALL_UNKNOWN);
+                }
+            }
         }
     }
 
@@ -1415,8 +1448,13 @@
                                                 + " an sdk library <"
                                                 + parsedPackage.getSdkLibraryName() + ">"
                                                 + " without changing the versionMajor, but the"
-                                                + " targetSdkVersion or minSdkVersion has changed."
-                                );
+                                                + " targetSdkVersion or minSdkVersion has changed:"
+                                                + " Old targetSdkVersion: " + oldTargetSdk
+                                                + " new targetSdkVersion: " + newTargetSdk
+                                                + " Old minSdkVersion: " + oldMinSdk
+                                                + " new minSdkVersion: " + newMinSdk
+                                                + " versionMajor: " + newVersionMajor
+                                    );
                             }
                         }
                     }
@@ -2231,8 +2269,9 @@
                 //  by apexd to be more accurate.
                 installRequest.setScannedPackageSettingFirstInstallTimeFromReplaced(
                         deletedPkgSetting, allUsers);
-                installRequest.setScannedPackageSettingLastUpdateTime(
-                        System.currentTimeMillis());
+                long currentTime = System.currentTimeMillis();
+                installRequest.setScannedPackageSettingLastUpdateTime(currentTime);
+                installRequest.setScannedPackageSettingFirstInstallTime(currentTime);
 
                 installRequest.getRemovedInfo().mBroadcastAllowList =
                         mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index dd2583a0d..ae7749b 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -58,6 +58,7 @@
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -867,6 +868,14 @@
         mScanResult.mPkgSetting.setLastUpdateTime(lastUpdateTim);
     }
 
+    public void setScannedPackageSettingFirstInstallTime(long firstInstallTime) {
+        assertScanResultExists();
+        PackageUserStateInternal userState = mScanResult.mPkgSetting.getUserStates().get(mUserId);
+        if (userState != null && userState.getFirstInstallTimeMillis() == 0) {
+            mScanResult.mPkgSetting.setFirstInstallTime(firstInstallTime, mUserId);
+        }
+    }
+
     public void setRemovedAppId(int appId) {
         if (mRemovedInfo != null) {
             mRemovedInfo.mUid = appId;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2124ff6..ff9c3e5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -60,6 +60,7 @@
 import android.app.ApplicationPackageManager;
 import android.app.BroadcastOptions;
 import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.SecurityLog;
 import android.app.backup.IBackupManager;
@@ -3372,8 +3373,10 @@
     // TODO(b/261957226): centralise this logic in DPM
     boolean isPackageDeviceAdmin(String packageName, int userId) {
         final IDevicePolicyManager dpm = getDevicePolicyManager();
+        final DevicePolicyManagerInternal dpmi =
+                mInjector.getLocalService(DevicePolicyManagerInternal.class);
         try {
-            if (dpm != null) {
+            if (dpm != null && dpmi != null) {
                 final ComponentName deviceOwnerComponentName = dpm.getDeviceOwnerComponent(
                         /* callingUserOnly =*/ false);
                 final String deviceOwnerPackageName = deviceOwnerComponentName == null ? null
@@ -3396,7 +3399,8 @@
                     if (dpm.packageHasActiveAdmins(packageName, users[i])) {
                         return true;
                     }
-                    if (isDeviceManagementRoleHolder(packageName, users[i])) {
+                    if (isDeviceManagementRoleHolder(packageName, users[i])
+                            && dpmi.isUserOrganizationManaged(users[i])) {
                         return true;
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index d374142..9fb9e71 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -42,6 +42,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
@@ -981,39 +982,23 @@
     }
 
     int[] queryInstalledUsers(int[] users, boolean installed) {
-        int num = 0;
+        IntArray installedUsers = new IntArray(users.length);
         for (int user : users) {
             if (getInstalled(user) == installed) {
-                num++;
+                installedUsers.add(user);
             }
         }
-        int[] res = new int[num];
-        num = 0;
-        for (int user : users) {
-            if (getInstalled(user) == installed) {
-                res[num] = user;
-                num++;
-            }
-        }
-        return res;
+        return installedUsers.toArray();
     }
 
     int[] queryUsersInstalledOrHasData(int[] users) {
-        int num = 0;
+        IntArray usersInstalledOrHasData = new IntArray(users.length);
         for (int user : users) {
             if (getInstalled(user) || readUserState(user).dataExists()) {
-                num++;
+                usersInstalledOrHasData.add(user);
             }
         }
-        int[] res = new int[num];
-        num = 0;
-        for (int user : users) {
-            if (getInstalled(user) || readUserState(user).dataExists()) {
-                res[num] = user;
-                num++;
-            }
-        }
-        return res;
+        return usersInstalledOrHasData.toArray();
     }
 
     long getCeDataInode(int userId) {
@@ -1283,25 +1268,14 @@
     }
 
     public int[] getNotInstalledUserIds() {
-        int count = 0;
         int userStateCount = mUserStates.size();
+        IntArray notInstalledUsers = new IntArray(userStateCount);
         for (int i = 0; i < userStateCount; i++) {
             if (!mUserStates.valueAt(i).isInstalled()) {
-                count++;
+                notInstalledUsers.add(mUserStates.keyAt(i));
             }
         }
-        if (count == 0) {
-            return EmptyArray.INT;
-        }
-
-        int[] excludedUserIds = new int[count];
-        int idx = 0;
-        for (int i = 0; i < userStateCount; i++) {
-            if (!mUserStates.valueAt(i).isInstalled()) {
-                excludedUserIds[idx++] = mUserStates.keyAt(i);
-            }
-        }
-        return excludedUserIds;
+        return notInstalledUsers.toArray();
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 26da84f..f9a8968 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -292,8 +292,10 @@
         // Step 2: destroy app data.
         mAppDataHelper.destroyAppDataLIF(resolvedPkg, userId, appDataDeletionFlags);
         if (userId != UserHandle.USER_ALL) {
-            ps.setCeDataInode(-1, userId);
-            ps.setDeDataInode(-1, userId);
+            synchronized (mPm.mLock) {
+                ps.setCeDataInode(-1, userId);
+                ps.setDeDataInode(-1, userId);
+            }
         }
 
         final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm,
@@ -425,19 +427,21 @@
             }
             final boolean isArchive = (flags & PackageManager.DELETE_ARCHIVE) != 0;
             final long currentTimeMillis = System.currentTimeMillis();
-            for (int userId : outInfo.mRemovedUsers) {
-                if (DEBUG_REMOVE) {
-                    final boolean wasInstalled = deletedPs.getInstalled(userId);
-                    Slog.d(TAG, "    user " + userId + ": " + wasInstalled + " => " + false);
+            synchronized (mPm.mLock) {
+                for (int userId : outInfo.mRemovedUsers) {
+                    if (DEBUG_REMOVE) {
+                        final boolean wasInstalled = deletedPs.getInstalled(userId);
+                        Slog.d(TAG, "    user " + userId + ": " + wasInstalled + " => " + false);
+                    }
+                    deletedPs.setInstalled(/* installed= */ false, userId);
                 }
-                deletedPs.setInstalled(/* installed= */ false, userId);
-            }
 
-            // Preserve split apk information for downgrade check with DELETE_KEEP_DATA and archived
-            // app cases
-            if (deletedPkg != null && deletedPkg.getSplitNames() != null) {
-                deletedPs.setSplitNames(deletedPkg.getSplitNames());
-                deletedPs.setSplitRevisionCodes(deletedPkg.getSplitRevisionCodes());
+                // Preserve split apk information for downgrade check with DELETE_KEEP_DATA and
+                // archived app cases
+                if (deletedPkg != null && deletedPkg.getSplitNames() != null) {
+                    deletedPs.setSplitNames(deletedPkg.getSplitNames());
+                    deletedPs.setSplitRevisionCodes(deletedPkg.getSplitRevisionCodes());
+                }
             }
         }
 
@@ -448,17 +452,19 @@
             if (DEBUG_REMOVE) {
                 Slog.d(TAG, "Propagating install state across downgrade");
             }
-            for (int userId : allUserHandles) {
-                final boolean installed = ArrayUtils.contains(outInfo.mOrigUsers, userId);
-                if (DEBUG_REMOVE) {
-                    Slog.d(TAG, "    user " + userId + " => " + installed);
-                }
-                if (installed != deletedPs.getInstalled(userId)) {
-                    installedStateChanged = true;
-                }
-                deletedPs.setInstalled(installed, userId);
-                if (installed) {
-                    deletedPs.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
+            synchronized (mPm.mLock) {
+                for (int userId : allUserHandles) {
+                    final boolean installed = ArrayUtils.contains(outInfo.mOrigUsers, userId);
+                    if (DEBUG_REMOVE) {
+                        Slog.d(TAG, "    user " + userId + " => " + installed);
+                    }
+                    if (installed != deletedPs.getInstalled(userId)) {
+                        installedStateChanged = true;
+                    }
+                    deletedPs.setInstalled(installed, userId);
+                    if (installed) {
+                        deletedPs.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 61fddba..0802e9e 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -443,17 +443,16 @@
 
         // Take care of first install / last update times.
         final long scanFileTime = getLastModifiedTime(parsedPackage);
-        final long existingFirstInstallTime = userId == UserHandle.USER_ALL
-                ? PackageStateUtils.getEarliestFirstInstallTime(pkgSetting.getUserStates())
-                : pkgSetting.readUserState(userId).getFirstInstallTimeMillis();
+        final long earliestFirstInstallTime =
+                PackageStateUtils.getEarliestFirstInstallTime((pkgSetting.getUserStates()));
         if (currentTime != 0) {
-            if (existingFirstInstallTime == 0) {
+            if (earliestFirstInstallTime == 0) {
                 pkgSetting.setFirstInstallTime(currentTime, userId)
                         .setLastUpdateTime(currentTime);
             } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
                 pkgSetting.setLastUpdateTime(currentTime);
             }
-        } else if (existingFirstInstallTime == 0) {
+        } else if (earliestFirstInstallTime == 0) {
             // We need *something*.  Take time stamp of the file.
             pkgSetting.setFirstInstallTime(scanFileTime, userId)
                     .setLastUpdateTime(scanFileTime);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4c9be21..1f672a0 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -5575,18 +5575,18 @@
                     }
                     if (!paths.getOverlayPaths().isEmpty()) {
                         pw.print(prefix);
-                        pw.println("    ");
+                        pw.print("    ");
                         pw.print(libOverlayPaths.getKey());
                         pw.println(" overlay paths:");
                         for (String path : paths.getOverlayPaths()) {
                             pw.print(prefix);
-                            pw.print("        ");
+                            pw.print("      ");
                             pw.println(path);
                         }
                     }
                     if (!paths.getResourceDirs().isEmpty()) {
                         pw.print(prefix);
-                        pw.println("      ");
+                        pw.print("    ");
                         pw.print(libOverlayPaths.getKey());
                         pw.println(" legacy overlay paths:");
                         for (String path : paths.getResourceDirs()) {
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 00582bf..045d4db 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -282,6 +282,12 @@
             for (int j = 0; j < idSize; j++) {
                 ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j));
             }
+            if (ShortcutService.DEBUG_REBOOT) {
+                Slog.d(TAG, "Persist shortcut ids pinned by "
+                    + getPackageName() + " from "
+                    + up.userId + "@" + up.packageName + " ids=["
+                    + String.join(", ", ids) + "]");
+            }
             out.endTag(null, TAG_PACKAGE);
         }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 84674b2..60056eb 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1850,9 +1850,17 @@
             }
             getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
 
+            if (ShortcutService.DEBUG_REBOOT) {
+                Slog.d(TAG, "Persisting shortcuts from "
+                    + getOwnerUserId() + "@" + getPackageName());
+            }
             for (int j = 0; j < size; j++) {
+                final ShortcutInfo si = mShortcuts.valueAt(j);
                 saveShortcut(
-                        out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed());
+                        out, si, forBackup, getPackageInfo().isBackupAllowed());
+                if (ShortcutService.DEBUG_REBOOT) {
+                    Slog.d(TAG, si.toSimpleString());
+                }
             }
 
             if (!forBackup) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 021f7aa..a3ff195 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -172,7 +172,7 @@
     static final boolean DEBUG = false; // STOPSHIP if true
     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
     static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
-    static final boolean DEBUG_REBOOT = true;
+    static final boolean DEBUG_REBOOT = Build.IS_DEBUGGABLE;
 
     @VisibleForTesting
     static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -588,7 +588,7 @@
 
     void handleOnDefaultLauncherChanged(int userId) {
         if (DEBUG) {
-            Slog.v(TAG, "Default launcher changed for user: " + userId);
+            Slog.v(TAG, "Default launcher changed for userId=" + userId);
         }
 
         // Default launcher is removed or changed, revoke all URI permissions.
@@ -712,7 +712,7 @@
     /** lifecycle event */
     void handleUnlockUser(int userId) {
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "handleUnlockUser: user=" + userId);
+            Slog.d(TAG, "handleUnlockUser: userId=" + userId);
         }
         synchronized (mUnlockedUsers) {
             mUnlockedUsers.put(userId, true);
@@ -739,7 +739,7 @@
     /** lifecycle event */
     void handleStopUser(int userId) {
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "handleStopUser: user=" + userId);
+            Slog.d(TAG, "handleStopUser: userId=" + userId);
         }
         Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "shortcutHandleStopUser");
         synchronized (mServiceLock) {
@@ -755,7 +755,7 @@
     @GuardedBy("mServiceLock")
     private void unloadUserLocked(int userId) {
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "unloadUserLocked: user=" + userId);
+            Slog.d(TAG, "unloadUserLocked: userId=" + userId);
         }
         // Cancel any ongoing background tasks.
         getUserShortcutsLocked(userId).cancelAllInFlightTasks();
@@ -1221,7 +1221,7 @@
 
     private void scheduleSaveInner(@UserIdInt int userId) {
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "Scheduling to save for " + userId);
+            Slog.d(TAG, "Scheduling to save for userId=" + userId);
         }
         synchronized (mServiceLock) {
             if (!mDirtyUserIds.contains(userId)) {
@@ -1333,7 +1333,7 @@
     // Requires mServiceLock held, but "Locked" prefix would look weird so we just say "L".
     void throwIfUserLockedL(@UserIdInt int userId) {
         if (!isUserUnlockedL(userId)) {
-            throw new IllegalStateException("User " + userId + " is locked or not running");
+            throw new IllegalStateException("User (with userId=" + userId + ") is locked or not running");
         }
     }
 
@@ -1720,7 +1720,7 @@
 
         // Otherwise, make sure the arguments are valid.
         if (UserHandle.getUserId(callingUid) != userId) {
-            throw new SecurityException("Invalid user-ID");
+            throw new SecurityException("Invalid userId");
         }
     }
 
@@ -1735,7 +1735,7 @@
 
         // Otherwise, make sure the arguments are valid.
         if (UserHandle.getUserId(callingUid) != userId) {
-            throw new SecurityException("Invalid user-ID");
+            throw new SecurityException("Invalid userId");
         }
         if (injectGetPackageUid(packageName, userId) != callingUid) {
             throw new SecurityException("Calling package name mismatch");
@@ -1755,7 +1755,7 @@
         }
         final int callingUid = injectBinderCallingUid();
         if (UserHandle.getUserId(callingUid) != si.getUserId()) {
-            throw new SecurityException("User-ID in shortcut doesn't match the caller");
+            throw new SecurityException("UserId in shortcut doesn't match the caller");
         }
     }
 
@@ -1822,7 +1822,7 @@
         final int userId = sp.getPackageUserId();
         if (DEBUG) {
             Slog.d(TAG, String.format(
-                    "Shortcut changes: package=%s, user=%d", packageName, userId));
+                    "Shortcut changes: package=%s, userId=%d", packageName, userId));
         }
         injectPostToHandlerDebounced(sp, notifyListenerRunnable(packageName, userId));
         notifyShortcutChangeCallbacks(packageName, userId, changedShortcuts, removedShortcuts);
@@ -1832,7 +1832,7 @@
     private void notifyListeners(@NonNull final String packageName, @UserIdInt final int userId) {
         if (DEBUG) {
             Slog.d(TAG, String.format(
-                    "Shortcut changes: package=%s, user=%d", packageName, userId));
+                    "Shortcut changes: package=%s, userId=%d", packageName, userId));
         }
         injectPostToHandler(notifyListenerRunnable(packageName, userId));
     }
@@ -2736,14 +2736,14 @@
     void resetThrottlingInner(@UserIdInt int userId) {
         synchronized (mServiceLock) {
             if (!isUserUnlockedL(userId)) {
-                Log.w(TAG, "User " + userId + " is locked or not running");
+                Log.w(TAG, "User (with userId=" + userId + ") is locked or not running");
                 return;
             }
 
             getUserShortcutsLocked(userId).resetThrottling();
         }
         scheduleSaveUser(userId);
-        Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId);
+        Slog.i(TAG, "ShortcutManager: throttling counter reset for userId=" + userId);
     }
 
     void resetAllThrottlingInner() {
@@ -2755,7 +2755,7 @@
     @Override
     public void onApplicationActive(String packageName, int userId) {
         if (DEBUG) {
-            Slog.d(TAG, "onApplicationActive: package=" + packageName + "  userid=" + userId);
+            Slog.d(TAG, "onApplicationActive: package=" + packageName + "  userId=" + userId);
         }
         enforceResetThrottlingPermission();
         synchronized (mServiceLock) {
@@ -2822,7 +2822,7 @@
 
             if (defaultLauncher != null) {
                 if (DEBUG) {
-                    Slog.v(TAG, "Detected launcher: " + defaultLauncher + " user: " + userId);
+                    Slog.v(TAG, "Detected launcher: " + defaultLauncher + " userId=" + userId);
                 }
                 return defaultLauncher.equals(packageName);
             } else {
@@ -2875,11 +2875,11 @@
                 if (defaultLauncher != null) {
                     if (DEBUG) {
                         Slog.v(TAG, "Default launcher from RoleManager: " + defaultLauncher
-                                + " user: " + userId);
+                                + " userId=" + userId);
                     }
                     user.setCachedLauncher(defaultLauncher);
                 } else {
-                    Slog.e(TAG, "Default launcher not found." + " user: " + userId);
+                    Slog.e(TAG, "Default launcher not found." + " userId=" + userId);
                 }
 
                 return defaultLauncher;
@@ -2974,7 +2974,7 @@
                 int queryFlags, int userId, int callingPid, int callingUid) {
             if (DEBUG_REBOOT) {
                 Slog.d(TAG, "Getting shortcuts for launcher= " + callingPackage
-                        + "user=" + userId + " pkg=" + packageName);
+                        + "userId=" + userId + " pkg=" + packageName);
             }
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
 
@@ -3793,7 +3793,7 @@
                     if (!isUserUnlockedL(userId)) {
                         if (DEBUG) {
                             Slog.d(TAG, "Ignoring package broadcast " + action
-                                    + " for locked/stopped user " + userId);
+                                    + " for locked/stopped userId=" + userId);
                         }
                         return;
                     }
@@ -3814,10 +3814,10 @@
                 switch (action) {
                     case Intent.ACTION_PACKAGE_ADDED:
                         if (replacing) {
-                            Slog.d(TAG, "replacing package: " + packageName + " userId" + userId);
+                            Slog.d(TAG, "replacing package: " + packageName + " userId=" + userId);
                             handlePackageUpdateFinished(packageName, userId);
                         } else {
-                            Slog.d(TAG, "adding package: " + packageName + " userId" + userId);
+                            Slog.d(TAG, "adding package: " + packageName + " userId=" + userId);
                             handlePackageAdded(packageName, userId);
                         }
                         break;
@@ -3825,21 +3825,21 @@
                         if (!replacing || (replacing && archival)) {
                             if (!replacing) {
                                 Slog.d(TAG, "removing package: "
-                                        + packageName + " userId" + userId);
+                                        + packageName + " userId=" + userId);
                             } else if (archival) {
                                 Slog.d(TAG, "archiving package: "
-                                        + packageName + " userId" + userId);
+                                        + packageName + " userId=" + userId);
                             }
                             handlePackageRemoved(packageName, userId);
                         }
                         break;
                     case Intent.ACTION_PACKAGE_CHANGED:
-                        Slog.d(TAG, "changing package: " + packageName + " userId" + userId);
+                        Slog.d(TAG, "changing package: " + packageName + " userId=" + userId);
                         handlePackageChanged(packageName, userId);
                         break;
                     case Intent.ACTION_PACKAGE_DATA_CLEARED:
                         Slog.d(TAG, "clearing data for package: "
-                                + packageName + " userId" + userId);
+                                + packageName + " userId=" + userId);
                         handlePackageDataCleared(packageName, userId);
                         break;
                 }
@@ -3902,7 +3902,7 @@
                     if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
                         if (DEBUG) {
                             Slog.d(TAG, "Uninstalled: " + spi.getPackageName()
-                                    + " user " + spi.getPackageUserId());
+                                    + " userId=" + spi.getPackageUserId());
                         }
                         gonePackages.add(
                                 UserPackage.of(spi.getPackageUserId(), spi.getPackageName()));
@@ -3927,7 +3927,7 @@
     @GuardedBy("mServiceLock")
     private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) {
         if (DEBUG_REBOOT) {
-            Slog.d(TAG, "rescan updated package user=" + userId + " last scanned=" + lastScanTime);
+            Slog.d(TAG, "rescan updated package userId=" + userId + " last scanned=" + lastScanTime);
         }
         final ShortcutUser user = getUserShortcutsLocked(userId);
 
@@ -3953,7 +3953,7 @@
 
     private void handlePackageAdded(String packageName, @UserIdInt int userId) {
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
+            Slog.d(TAG, String.format("handlePackageAdded: %s userId=%d", packageName, userId));
         }
         synchronized (mServiceLock) {
             final ShortcutUser user = getUserShortcutsLocked(userId);
@@ -3965,7 +3965,7 @@
 
     private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
+            Slog.d(TAG, String.format("handlePackageUpdateFinished: %s userId=%d",
                     packageName, userId));
         }
         synchronized (mServiceLock) {
@@ -3981,7 +3981,7 @@
 
     private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
+            Slog.d(TAG, String.format("handlePackageRemoved: %s userId=%d", packageName,
                     packageUserId));
         }
         cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false);
@@ -3991,7 +3991,7 @@
 
     private void handlePackageDataCleared(String packageName, int packageUserId) {
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
+            Slog.d(TAG, String.format("handlePackageDataCleared: %s userId=%d", packageName,
                     packageUserId));
         }
         cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true);
@@ -4006,7 +4006,7 @@
             return;
         }
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName,
+            Slog.d(TAG, String.format("handlePackageChanged: %s userId=%d", packageName,
                     packageUserId));
         }
 
@@ -4193,7 +4193,7 @@
     private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, boolean afterOta,
             Consumer<ApplicationInfo> callback) {
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime
+            Slog.d(TAG, "forUpdatedPackages for userId=" + userId + ", lastScanTime=" + lastScanTime
                     + " afterOta=" + afterOta);
         }
         final List<PackageInfo> list = getInstalledPackages(userId);
@@ -4302,7 +4302,7 @@
             return mContext.createContextAsUser(UserHandle.of(userId), /* flags */ 0)
                     .getPackageManager().getResourcesForApplication(packageName);
         } catch (NameNotFoundException e) {
-            Slog.e(TAG, "Resources of package " + packageName + " for user " + userId
+            Slog.e(TAG, "Resources of package " + packageName + " for userId=" + userId
                     + " not found");
             return null;
         } finally {
@@ -4512,17 +4512,17 @@
     public byte[] getBackupPayload(@UserIdInt int userId) {
         enforceSystem();
         if (DEBUG) {
-            Slog.d(TAG, "Backing up user " + userId);
+            Slog.d(TAG, "Backing up user with userId=" + userId);
         }
         synchronized (mServiceLock) {
             if (!isUserUnlockedL(userId)) {
-                wtf("Can't backup: user " + userId + " is locked or not running");
+                wtf("Can't backup: userId=" + userId + " is locked or not running");
                 return null;
             }
 
             final ShortcutUser user = getUserShortcutsLocked(userId);
             if (user == null) {
-                wtf("Can't backup: user not found: id=" + userId);
+                wtf("Can't backup: user not found: userId=" + userId);
                 return null;
             }
 
@@ -4562,11 +4562,11 @@
     public void applyRestore(byte[] payload, @UserIdInt int userId) {
         enforceSystem();
         if (DEBUG || DEBUG_REBOOT) {
-            Slog.d(TAG, "Restoring user " + userId);
+            Slog.d(TAG, "Restoring user with userId=" + userId);
         }
         synchronized (mServiceLock) {
             if (!isUserUnlockedL(userId)) {
-                wtf("Can't restore: user " + userId + " is locked or not running");
+                wtf("Can't restore: user (with userId=" + userId + ") is locked or not running");
                 return;
             }
             // Note we print the file timestamps in dumpsys too, but also printing the timestamp
@@ -4989,7 +4989,7 @@
                             mUserId = UserHandle.parseUserArg(getNextArgRequired());
                             if (!isUserUnlockedL(mUserId)) {
                                 throw new CommandException(
-                                        "User " + mUserId + " is not running or locked");
+                                        "User (with userId=" + mUserId + ") is not running or locked");
                             }
                             break;
                         }
@@ -5094,7 +5094,7 @@
             synchronized (mServiceLock) {
                 parseOptionsLocked(/* takeUser =*/ true);
 
-                Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId);
+                Slog.i(TAG, "cmd: handleResetThrottling: userId=" + mUserId);
 
                 resetThrottlingInner(mUserId);
             }
@@ -5136,7 +5136,7 @@
                 final String defaultLauncher = getDefaultLauncher(mUserId);
                 if (defaultLauncher == null) {
                     throw new CommandException(
-                            "Failed to get the default launcher for user " + mUserId);
+                            "Failed to get the default launcher for userId=" + mUserId);
                 }
 
                 // Get the class name of the component from PM to keep the old behaviour.
@@ -5157,7 +5157,7 @@
             synchronized (mServiceLock) {
                 parseOptionsLocked(/* takeUser =*/ true);
 
-                Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId);
+                Slog.i(TAG, "cmd: handleUnloadUser: userId=" + mUserId);
 
                 ShortcutService.this.handleStopUser(mUserId);
             }
@@ -5168,7 +5168,7 @@
                 parseOptionsLocked(/* takeUser =*/ true);
                 final String packageName = getNextArgRequired();
 
-                Slog.i(TAG, "cmd: handleClearShortcuts: user" + mUserId + ", " + packageName);
+                Slog.i(TAG, "cmd: handleClearShortcuts: userId=" + mUserId + ", " + packageName);
 
                 ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId,
                         /* appStillExists = */ true);
@@ -5180,7 +5180,7 @@
                 parseOptionsLocked(/* takeUser =*/ true);
                 final String packageName = getNextArgRequired();
 
-                Slog.i(TAG, "cmd: handleGetShortcuts: user=" + mUserId + ", flags="
+                Slog.i(TAG, "cmd: handleGetShortcuts: userId=" + mUserId + ", flags="
                         + mShortcutMatchFlags + ", package=" + packageName);
 
                 final ShortcutUser user = ShortcutService.this.getUserShortcutsLocked(mUserId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2b639fa..13901c1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -25,6 +25,7 @@
 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
+import static android.os.UserHandle.USER_SYSTEM;
 import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
@@ -1193,11 +1194,11 @@
             // Avoid marking pre-created users for removal.
             return;
         }
-        if (ui.lastLoggedInTime == 0 && ui.isGuest() && Resources.getSystem().getBoolean(
-                com.android.internal.R.bool.config_guestUserAutoCreated)) {
-            // Avoid marking auto-created but not-yet-logged-in guest user for removal. Because a
-            // new one will be created anyway, and this one doesn't have any personal data in it yet
-            // due to not being logged in.
+        if (ui.lastLoggedInTime == 0) {
+            // Avoid marking a not-yet-logged-in ephemeral user for removal, since it doesn't have
+            // any personal data in it yet due to not being logged in.
+            // This will also avoid marking an auto-created not-yet-logged-in ephemeral guest user
+            // for removal, which would be recreated again later in the boot anyway.
             return;
         }
         // Mark the user for removal.
@@ -1372,6 +1373,10 @@
         }
 
         if (isHeadlessSystemUserMode()) {
+            if (mContext.getResources()
+                    .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) {
+                return UserHandle.USER_SYSTEM;
+            }
             // Return the previous foreground user, if there is one.
             final int previousUser = getPreviousFullUserToEnterForeground();
             if (previousUser != UserHandle.USER_NULL) {
@@ -2514,6 +2519,38 @@
     }
 
     /**
+     * This method validates whether calling user is valid in visible background users feature.
+     * Valid user is the current user or the system or in the same profile group as the current
+     * user. Visible background users are not valid calling users.
+     */
+    public static void enforceCurrentUserIfVisibleBackgroundEnabled(@UserIdInt int currentUserId) {
+        if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+            return;
+        }
+        final int callingUserId = UserHandle.getCallingUserId();
+        if (DBG) {
+            Slog.d(LOG_TAG, "enforceValidCallingUser: callingUserId=" + callingUserId
+                    + " isSystemUser=" + (callingUserId == USER_SYSTEM)
+                    + " currentUserId=" + currentUserId
+                    + " callingPid=" + Binder.getCallingPid()
+                    + " callingUid=" + Binder.getCallingUid());
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (callingUserId != USER_SYSTEM && callingUserId != currentUserId
+                    && !UserManagerService.getInstance()
+                    .isSameProfileGroup(callingUserId, currentUserId)) {
+                throw new SecurityException(
+                        "Invalid calling user on devices that enable visible background users. "
+                                + "callingUserId=" + callingUserId + " currentUserId="
+                                + currentUserId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
      * Gets the current and target user ids as a {@link Pair}, calling
      * {@link ActivityManagerInternal} directly (and without performing any permission check).
      *
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/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
index f518769..e9cb279 100644
--- a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
+++ b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
@@ -375,24 +375,34 @@
                 @Nullable String message, boolean shouldCollectMessage, boolean skiProxyOperation,
                 @NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean,
                         Boolean, SyncNotedAppOp> superImpl) {
-            if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) {
-                final int shellUid = UserHandle.getUid(
-                        UserHandle.getUserId(attributionSource.getUid()), Process.SHELL_UID);
-                final long identity = Binder.clearCallingIdentity();
-                try {
-                    return superImpl.apply(code,
-                            new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG,
-                                    attributionSource.getAttributionTag(),
-                                    attributionSource.getToken(), /*renouncedPermissions*/ null,
-                                    attributionSource.getDeviceId(), attributionSource.getNext()),
-                            shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                            skiProxyOperation);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
+            if (!isDelegateOp(code)) {
+                return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp,
+                        message, shouldCollectMessage, skiProxyOperation);
             }
-            return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp,
-                    message, shouldCollectMessage, skiProxyOperation);
+
+            final int shellUid = UserHandle.getUid(
+                    UserHandle.getUserId(attributionSource.getUid()), Process.SHELL_UID);
+            AttributionSource next = attributionSource.getNext();
+            if (next != null && next.getUid() == mDelegateAndOwnerUid) {
+                next = new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG,
+                        next.getAttributionTag(), next.getToken(), /*renouncedPermissions*/ null,
+                        next.getDeviceId(), next.getNext());
+                attributionSource = new AttributionSource(attributionSource, next);
+            }
+            if (attributionSource.getUid() == mDelegateAndOwnerUid) {
+                attributionSource = new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG,
+                        attributionSource.getAttributionTag(),
+                        attributionSource.getToken(), /*renouncedPermissions*/ null,
+                        attributionSource.getDeviceId(), attributionSource.getNext());
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return superImpl.apply(code, attributionSource,
+                        shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                        skiProxyOperation);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6c78b3c..6e7a009 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1366,7 +1366,7 @@
 
         for (int requestedPermissionNum = 0; requestedPermissionNum < numRequestedPermissions;
                 requestedPermissionNum++) {
-            String permission = requestedPermissions[requestedPermissionNum];
+            String permission = sortedRequestedPermissions[requestedPermissionNum];
 
             // If there is a disabled system app it may request a permission the updated
             // version ot the data partition doesn't, In this case skip the permission.
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 46e6546..df9f7fb 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,7 +26,6 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
 import static android.permission.flags.Flags.serverSideAttributionRegistration;
@@ -1640,22 +1639,7 @@
                         throw new SecurityException(msg + ":" + e.getMessage());
                     }
                 }
-                int result = Math.max(checkedOpResult, notedOpResult);
-                // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
-                    if (result == checkedOpResult) {
-                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
-                                + " checkOp for resolvedAttributionSource "
-                                + resolvedAttributionSource + " and op " + op
-                                + " returned MODE_ERRORED");
-                    } else {
-                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
-                                + " noteOp for resolvedAttributionSource "
-                                + resolvedAttributionSource + " and op " + notedOp
-                                + " returned MODE_ERRORED");
-                    }
-                }
-                return result;
+                return Math.max(checkedOpResult, notedOpResult);
             }
         }
 
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/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 7ed8972..027e69c 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -17,7 +17,9 @@
 package com.android.server.policy;
 
 import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
+import static com.android.hardware.input.Flags.modifierShortcutManagerRefactor;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.role.RoleManager;
@@ -37,6 +39,7 @@
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.LongSparseArray;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.KeyCharacterMap;
@@ -81,8 +84,8 @@
     private static final String ATTRIBUTE_SHIFT = "shift";
     private static final String ATTRIBUTE_ROLE = "role";
 
-    private final SparseArray<Intent> mIntentShortcuts = new SparseArray<>();
-    private final SparseArray<Intent> mShiftShortcuts = new SparseArray<>();
+    private final SparseArray<Intent> mCategoryShortcuts = new SparseArray<>();
+    private final SparseArray<Intent> mShiftCategoryShortcuts = new SparseArray<>();
     private final SparseArray<String> mRoleShortcuts = new SparseArray<String>();
     private final SparseArray<String> mShiftRoleShortcuts = new SparseArray<String>();
     private final Map<String, Intent> mRoleIntents = new HashMap<String, Intent>();
@@ -127,6 +130,7 @@
     private boolean mSearchKeyShortcutPending = false;
     private boolean mConsumeSearchKeyUp = true;
     private UserHandle mCurrentUser;
+    private final Map<Pair<Character, Boolean>, Bookmark> mBookmarks = new HashMap<>();
 
     ModifierShortcutManager(Context context, Handler handler, UserHandle currentUser) {
         mContext = context;
@@ -134,7 +138,14 @@
         RoleManager rm = mContext.getSystemService(RoleManager.class);
         rm.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
                 (String roleName, UserHandle user) -> {
-                    mRoleIntents.remove(roleName);
+                    if (modifierShortcutManagerRefactor()) {
+                        mBookmarks.values().stream().filter(b ->
+                                b instanceof RoleBookmark
+                                && ((RoleBookmark) b).getRole().equals(roleName))
+                                .forEach(Bookmark::clearIntent);
+                    } else {
+                        mRoleIntents.remove(roleName);
+                    }
                 }, UserHandle.ALL);
         mCurrentUser = currentUser;
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -146,8 +157,26 @@
 
         // Role based shortcuts may resolve to different apps for different users
         // so clear the cache.
-        mRoleIntents.clear();
-        mComponentIntents.clear();
+        clearRoleIntents();
+        clearComponentIntents();
+    }
+
+    void clearRoleIntents() {
+        if (modifierShortcutManagerRefactor()) {
+            mBookmarks.values().stream().filter(b ->
+                    b instanceof RoleBookmark).forEach(Bookmark::clearIntent);
+        } else {
+            mRoleIntents.clear();
+        }
+    }
+
+    void clearComponentIntents() {
+        if (modifierShortcutManagerRefactor()) {
+            mBookmarks.values().stream().filter(b ->
+                    b instanceof ComponentBookmark).forEach(Bookmark::clearIntent);
+        } else {
+            mComponentIntents.clear();
+        }
     }
 
     /**
@@ -176,77 +205,111 @@
 
         Intent shortcutIntent = null;
 
-        // If the Shift key is pressed, then search for the shift shortcuts.
-        SparseArray<Intent> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts;
-
         // First try the exact keycode (with modifiers).
         int shortcutChar = kcm.get(keyCode, metaState);
         if (shortcutChar == 0) {
             return null;
         }
-        shortcutIntent = shortcutMap.get(shortcutChar);
 
-        if (shortcutIntent == null) {
-            // Next try the primary character on that key.
-            shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
-            if (shortcutChar == 0) {
-                return null;
+        if (modifierShortcutManagerRefactor()) {
+            Bookmark bookmark = mBookmarks.get(new Pair<>((char) shortcutChar, isShiftOn));
+            if (bookmark == null) {
+                // Next try the primary character on that key.
+                shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+                if (shortcutChar == 0) {
+                    return null;
+                }
+                bookmark = mBookmarks.get(new Pair<>((char) shortcutChar, isShiftOn));
             }
+
+            if (bookmark != null) {
+                Context context = modifierShortcutManagerMultiuser()
+                        ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+                shortcutIntent = bookmark.getIntent(context);
+            } else {
+                Log.d(TAG, "No bookmark found for "
+                        + (isShiftOn ? "SHIFT+" : "") + (char) shortcutChar);
+            }
+        } else {
+            // If the Shift key is pressed, then search for the shift shortcuts.
+            SparseArray<Intent> shortcutMap = isShiftOn
+                    ? mShiftCategoryShortcuts : mCategoryShortcuts;
             shortcutIntent = shortcutMap.get(shortcutChar);
-        }
 
-        if (shortcutIntent == null) {
-            // Next check for role based shortcut with primary character.
-            String role = isShiftOn ? mShiftRoleShortcuts.get(shortcutChar)
-                    : mRoleShortcuts.get(shortcutChar);
-            if (role != null) {
-                shortcutIntent = getRoleLaunchIntent(role);
-            }
-        }
-
-        if (modifierShortcutManagerMultiuser()) {
             if (shortcutIntent == null) {
-                // Next check component based shortcuts with primary character.
-                ComponentName component = isShiftOn
-                        ? mShiftComponentShortcuts.get(shortcutChar)
-                        : mComponentShortcuts.get(shortcutChar);
-                if (component != null) {
-                    shortcutIntent = resolveComponentNameIntent(component);
+                // Next try the primary character on that key.
+                shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
+                if (shortcutChar == 0) {
+                    return null;
+                }
+                shortcutIntent = shortcutMap.get(shortcutChar);
+            }
+
+            if (shortcutIntent == null) {
+                // Next check for role based shortcut with primary character.
+                String role = isShiftOn ? mShiftRoleShortcuts.get(shortcutChar)
+                        : mRoleShortcuts.get(shortcutChar);
+                if (role != null) {
+                    shortcutIntent = getRoleLaunchIntent(role);
+                }
+            }
+
+            if (modifierShortcutManagerMultiuser()) {
+                if (shortcutIntent == null) {
+                    // Next check component based shortcuts with primary character.
+                    ComponentName component = isShiftOn
+                            ? mShiftComponentShortcuts.get(shortcutChar)
+                            : mComponentShortcuts.get(shortcutChar);
+                    if (component != null) {
+                        shortcutIntent = resolveComponentNameIntent(component);
+                    }
                 }
             }
         }
         return shortcutIntent;
     }
 
+    @Nullable
+    private static Intent getRoleLaunchIntent(Context context, String role) {
+        Intent intent = null;
+        RoleManager rm = context.getSystemService(RoleManager.class);
+        PackageManager pm = context.getPackageManager();
+        if (rm.isRoleAvailable(role)) {
+            String rolePackage = rm.getDefaultApplication(role);
+            if (rolePackage != null) {
+                intent = pm.getLaunchIntentForPackage(rolePackage);
+                if (intent != null) {
+                    intent.putExtra(EXTRA_ROLE, role);
+
+                } else {
+                    Log.w(TAG, "No launch intent for role " + role);
+                }
+            } else {
+                Log.w(TAG, "No default application for role "
+                        + role + " user=" + context.getUser());
+            }
+        } else {
+            Log.w(TAG, "Role " + role + " is not available.");
+        }
+        return intent;
+    }
+
+    @Nullable
     private Intent getRoleLaunchIntent(String role) {
         Intent intent = mRoleIntents.get(role);
         if (intent == null) {
             Context context = modifierShortcutManagerMultiuser()
                     ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
-            RoleManager rm = context.getSystemService(RoleManager.class);
-            PackageManager pm = context.getPackageManager();
-            if (rm.isRoleAvailable(role)) {
-                String rolePackage = rm.getDefaultApplication(role);
-                if (rolePackage != null) {
-                    intent = pm.getLaunchIntentForPackage(rolePackage);
-                    if (intent != null) {
-                        intent.putExtra(EXTRA_ROLE, role);
-                        mRoleIntents.put(role, intent);
-                    } else {
-                        Log.w(TAG, "No launch intent for role " + role);
-                    }
-                } else {
-                    Log.w(TAG, "No default application for role " + role);
-                }
-            } else {
-                Log.w(TAG, "Role " + role + " is not available.");
+            intent = getRoleLaunchIntent(context, role);
+            if (intent != null) {
+                mRoleIntents.put(role, intent);
             }
         }
+
         return intent;
     }
 
     private void loadShortcuts() {
-
         try {
             XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
             XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
@@ -276,57 +339,84 @@
                     continue;
                 }
 
-                final int shortcutChar = shortcutName.charAt(0);
                 final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
-                final Intent intent;
-                if (packageName != null && className != null) {
-                    if (roleName != null || categoryName != null) {
-                        Log.w(TAG, "Cannot specify role or category when package and class"
-                                + " are present for bookmark packageName=" + packageName
-                                + " className=" + className + " shortcutChar=" + shortcutChar);
-                        continue;
+
+                if (modifierShortcutManagerRefactor()) {
+                    final char shortcutChar = shortcutName.charAt(0);
+                    Bookmark bookmark = null;
+                    if (packageName != null && className != null) {
+                        bookmark = new ComponentBookmark(
+                                shortcutChar, isShiftShortcut, packageName, className);
+                    } else if (categoryName != null) {
+                        bookmark = new CategoryBookmark(
+                                shortcutChar, isShiftShortcut, categoryName);
+                    } else if (roleName != null) {
+                        bookmark = new RoleBookmark(shortcutChar, isShiftShortcut, roleName);
                     }
-                    if (modifierShortcutManagerMultiuser()) {
-                        ComponentName componentName = new ComponentName(packageName, className);
-                        if (isShiftShortcut) {
-                            mShiftComponentShortcuts.put(shortcutChar, componentName);
+                    if (bookmark != null) {
+                        Log.d(TAG, "adding shortcut " + bookmark + "shift="
+                                + isShiftShortcut + " char=" + shortcutChar);
+                        mBookmarks.put(new Pair<>(shortcutChar, isShiftShortcut), bookmark);
+                    }
+                } else {
+                    final int shortcutChar = shortcutName.charAt(0);
+                    if (packageName != null && className != null) {
+                        if (roleName != null || categoryName != null) {
+                            Log.w(TAG, "Cannot specify role or category when package and class"
+                                    + " are present for bookmark packageName=" + packageName
+                                    + " className=" + className + " shortcutChar=" + shortcutChar);
+                            continue;
+                        }
+                        if (modifierShortcutManagerMultiuser()) {
+                            ComponentName componentName =
+                                    new ComponentName(packageName, className);
+                            if (isShiftShortcut) {
+                                mShiftComponentShortcuts.put(shortcutChar, componentName);
+                            } else {
+                                mComponentShortcuts.put(shortcutChar, componentName);
+                            }
                         } else {
-                            mComponentShortcuts.put(shortcutChar, componentName);
+                            Intent intent = resolveComponentNameIntent(packageName, className);
+                            if (isShiftShortcut) {
+                                mShiftCategoryShortcuts.put(shortcutChar, intent);
+                            } else {
+                                mCategoryShortcuts.put(shortcutChar, intent);
+                            }
+                        }
+                        continue;
+                    } else if (categoryName != null) {
+                        if (roleName != null) {
+                            Log.w(TAG, "Cannot specify role bookmark when category is present for"
+                                    + " bookmark shortcutChar=" + shortcutChar
+                                    + " category= " + categoryName);
+                            continue;
+                        }
+                        Intent intent = Intent.makeMainSelectorActivity(
+                                Intent.ACTION_MAIN, categoryName);
+                        if (intent == null) {
+                            Log.w(TAG, "Null selector intent for " + categoryName);
+                        } else {
+                            if (isShiftShortcut) {
+                                mShiftCategoryShortcuts.put(shortcutChar, intent);
+                            } else {
+                                mCategoryShortcuts.put(shortcutChar, intent);
+                            }
+                        }
+                        continue;
+                    } else if (roleName != null) {
+                        // We can't resolve the role at the time of this file being parsed as the
+                        // device hasn't finished booting, so we will look it up lazily.
+                        if (isShiftShortcut) {
+                            mShiftRoleShortcuts.put(shortcutChar, roleName);
+                        } else {
+                            mRoleShortcuts.put(shortcutChar, roleName);
                         }
                         continue;
                     } else {
-                        intent = resolveComponentNameIntent(packageName, className);
-                    }
-                } else if (categoryName != null) {
-                    if (roleName != null) {
-                        Log.w(TAG, "Cannot specify role bookmark when category is present for"
-                                + " bookmark shortcutChar=" + shortcutChar
-                                + " category= " + categoryName);
+                        Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
+                                + ": missing package/class, category or role attributes");
                         continue;
                     }
-                    intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
-                    if (intent == null) {
-                        Log.w(TAG, "Null selector intent for " + categoryName);
-                    }
-                } else if (roleName != null) {
-                    // We can't resolve the role at the time of this file being parsed as the
-                    // device hasn't finished booting, so we will look it up lazily.
-                    if (isShiftShortcut) {
-                        mShiftRoleShortcuts.put(shortcutChar, roleName);
-                    } else {
-                        mRoleShortcuts.put(shortcutChar, roleName);
-                    }
-                    continue;
-                } else {
-                    Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName
-                            + ": missing package/class, category or role attributes");
-                    continue;
-                }
-
-                if (isShiftShortcut) {
-                    mShiftShortcuts.put(shortcutChar, intent);
-                } else {
-                    mIntentShortcuts.put(shortcutChar, intent);
                 }
             }
         } catch (XmlPullParserException | IOException e) {
@@ -336,21 +426,35 @@
 
     @Nullable
     private Intent resolveComponentNameIntent(ComponentName componentName) {
-        Intent intent = mComponentIntents.get(componentName);
-        if (intent == null) {
-            intent = resolveComponentNameIntent(
-                    componentName.getPackageName(), componentName.getClassName());
-            if (intent != null) {
-                mComponentIntents.put(componentName, intent);
+        if (modifierShortcutManagerRefactor()) {
+            return null;
+        } else {
+            Intent intent = mComponentIntents.get(componentName);
+            if (intent == null) {
+                intent = resolveComponentNameIntent(
+                        componentName.getPackageName(), componentName.getClassName());
+                if (intent != null) {
+                    mComponentIntents.put(componentName, intent);
+                }
             }
+            return intent;
         }
-        return intent;
     }
 
     @Nullable
     private Intent resolveComponentNameIntent(String packageName, String className) {
-        Context context = modifierShortcutManagerMultiuser()
-                ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+        if (modifierShortcutManagerRefactor()) {
+            return null;
+        } else {
+            Context context = modifierShortcutManagerMultiuser()
+                        ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+            return resolveComponentNameIntent(context, packageName, className);
+        }
+    }
+
+    @Nullable
+    private static Intent resolveComponentNameIntent(
+            Context context, String packageName, String className) {
         PackageManager pm = context.getPackageManager();
         int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
         if (!modifierShortcutManagerMultiuser()) {
@@ -562,64 +666,81 @@
      */
     public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
         List<KeyboardShortcutInfo> shortcuts = new ArrayList();
-        for (int i = 0; i <  mIntentShortcuts.size(); i++) {
-            KeyboardShortcutInfo info = shortcutInfoFromIntent(
-                    (char) (mIntentShortcuts.keyAt(i)), mIntentShortcuts.valueAt(i), false);
-            if (info != null) {
-                shortcuts.add(info);
-            }
-        }
-
-        for (int i = 0; i <  mShiftShortcuts.size(); i++) {
-            KeyboardShortcutInfo info = shortcutInfoFromIntent(
-                    (char) (mShiftShortcuts.keyAt(i)), mShiftShortcuts.valueAt(i), true);
-            if (info != null) {
-                shortcuts.add(info);
-            }
-        }
-
-        for (int i = 0; i <  mRoleShortcuts.size(); i++) {
-            String role = mRoleShortcuts.valueAt(i);
-            KeyboardShortcutInfo info = shortcutInfoFromIntent(
-                    (char) (mRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), false);
-            if (info != null) {
-                shortcuts.add(info);
-            }
-        }
-
-        for (int i = 0; i <  mShiftRoleShortcuts.size(); i++) {
-            String role = mShiftRoleShortcuts.valueAt(i);
-            KeyboardShortcutInfo info = shortcutInfoFromIntent(
-                    (char) (mShiftRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), true);
-            if (info != null) {
-                shortcuts.add(info);
-            }
-        }
-
-        if (modifierShortcutManagerMultiuser()) {
-            for (int i = 0; i < mComponentShortcuts.size(); i++) {
-                ComponentName component = mComponentShortcuts.valueAt(i);
+        if (modifierShortcutManagerRefactor()) {
+            for (Bookmark b : mBookmarks.values()) {
                 KeyboardShortcutInfo info = shortcutInfoFromIntent(
-                        (char) (mComponentShortcuts.keyAt(i)),
-                        resolveComponentNameIntent(component),
+                        b.getShortcutChar(), b.getIntent(mContext), b.isShift());
+                if (info != null) {
+                    shortcuts.add(info);
+                }
+            }
+        } else {
+            for (int i = 0; i <  mCategoryShortcuts.size(); i++) {
+                KeyboardShortcutInfo info = shortcutInfoFromIntent(
+                        (char) (mCategoryShortcuts.keyAt(i)),
+                        mCategoryShortcuts.valueAt(i),
                         false);
                 if (info != null) {
                     shortcuts.add(info);
                 }
             }
 
-            for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
-                ComponentName component = mShiftComponentShortcuts.valueAt(i);
+            for (int i = 0; i <  mShiftCategoryShortcuts.size(); i++) {
                 KeyboardShortcutInfo info = shortcutInfoFromIntent(
-                        (char) (mShiftComponentShortcuts.keyAt(i)),
-                        resolveComponentNameIntent(component),
+                        (char) (mShiftCategoryShortcuts.keyAt(i)),
+                        mShiftCategoryShortcuts.valueAt(i),
                         true);
                 if (info != null) {
                     shortcuts.add(info);
                 }
             }
-        }
 
+            for (int i = 0; i <  mRoleShortcuts.size(); i++) {
+                String role = mRoleShortcuts.valueAt(i);
+                KeyboardShortcutInfo info = shortcutInfoFromIntent(
+                        (char) (mRoleShortcuts.keyAt(i)),
+                        getRoleLaunchIntent(role),
+                        false);
+                if (info != null) {
+                    shortcuts.add(info);
+                }
+            }
+
+            for (int i = 0; i <  mShiftRoleShortcuts.size(); i++) {
+                String role = mShiftRoleShortcuts.valueAt(i);
+                KeyboardShortcutInfo info = shortcutInfoFromIntent(
+                        (char) (mShiftRoleShortcuts.keyAt(i)),
+                        getRoleLaunchIntent(role),
+                        true);
+                if (info != null) {
+                    shortcuts.add(info);
+                }
+            }
+
+            if (modifierShortcutManagerMultiuser()) {
+                for (int i = 0; i < mComponentShortcuts.size(); i++) {
+                    ComponentName component = mComponentShortcuts.valueAt(i);
+                    KeyboardShortcutInfo info = shortcutInfoFromIntent(
+                            (char) (mComponentShortcuts.keyAt(i)),
+                            resolveComponentNameIntent(component),
+                            false);
+                    if (info != null) {
+                        shortcuts.add(info);
+                    }
+                }
+
+                for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
+                    ComponentName component = mShiftComponentShortcuts.valueAt(i);
+                    KeyboardShortcutInfo info = shortcutInfoFromIntent(
+                            (char) (mShiftComponentShortcuts.keyAt(i)),
+                            resolveComponentNameIntent(component),
+                            true);
+                    if (info != null) {
+                        shortcuts.add(info);
+                    }
+                }
+            }
+        }
         return new KeyboardShortcutGroup(
                 mContext.getString(R.string.keyboard_shortcut_group_applications),
                 shortcuts);
@@ -800,57 +921,171 @@
     void dump(String prefix, PrintWriter pw) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw,  "  ", prefix);
         ipw.println("ModifierShortcutManager shortcuts:");
-
-        ipw.increaseIndent();
-        ipw.println("Roles");
-        ipw.increaseIndent();
-        for (int i = 0; i <  mRoleShortcuts.size(); i++) {
-            String role = mRoleShortcuts.valueAt(i);
-            char shortcutChar = (char) mRoleShortcuts.keyAt(i);
-            Intent intent = getRoleLaunchIntent(role);
-            ipw.println(shortcutChar + " " + role + " " + intent);
-        }
-
-        for (int i = 0; i <  mShiftRoleShortcuts.size(); i++) {
-            String role = mShiftRoleShortcuts.valueAt(i);
-            char shortcutChar = (char) mShiftRoleShortcuts.keyAt(i);
-            Intent intent = getRoleLaunchIntent(role);
-            ipw.println("SHIFT+" + shortcutChar + " " + role + " " + intent);
-        }
-
-        ipw.decreaseIndent();
-        ipw.println("Selectors");
-        ipw.increaseIndent();
-        for (int i = 0; i <  mIntentShortcuts.size(); i++) {
-            char shortcutChar = (char) mIntentShortcuts.keyAt(i);
-            Intent intent = mIntentShortcuts.valueAt(i);
-            ipw.println(shortcutChar + " " + intent);
-        }
-
-        for (int i = 0; i <  mShiftShortcuts.size(); i++) {
-            char shortcutChar = (char) mShiftShortcuts.keyAt(i);
-            Intent intent = mShiftShortcuts.valueAt(i);
-            ipw.println("SHIFT+" + shortcutChar + " " + intent);
-
-        }
-
-        if (modifierShortcutManagerMultiuser()) {
-            ipw.decreaseIndent();
-            ipw.println("ComponentNames");
+        if (modifierShortcutManagerRefactor()) {
             ipw.increaseIndent();
-            for (int i = 0; i < mComponentShortcuts.size(); i++) {
-                char shortcutChar = (char) mComponentShortcuts.keyAt(i);
-                ComponentName component = mComponentShortcuts.valueAt(i);
-                Intent intent = resolveComponentNameIntent(component);
-                ipw.println(shortcutChar + " " + component + " " + intent);
+            for (Bookmark b : mBookmarks.values()) {
+                boolean isShift = b.isShift();
+                char shortcutChar = b.getShortcutChar();
+                Context context = modifierShortcutManagerMultiuser()
+                        ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
+
+                Intent intent = b.getIntent(context);
+                ipw.print(isShift ? "SHIFT+" : "");
+                ipw.println(shortcutChar + " " + intent);
+                ipw.increaseIndent();
+                ipw.increaseIndent();
+                KeyboardShortcutInfo info = shortcutInfoFromIntent(shortcutChar, intent, isShift);
+                if (info != null) {
+                    ipw.println("Resolves to: " + info.getLabel());
+                } else {
+                    ipw.println("<No KeyboardShortcutInfo available for this shortcut>");
+                }
+                ipw.decreaseIndent();
+                ipw.decreaseIndent();
+            }
+        } else {
+            ipw.increaseIndent();
+            ipw.println("Roles");
+            ipw.increaseIndent();
+            for (int i = 0; i < mRoleShortcuts.size(); i++) {
+                String role = mRoleShortcuts.valueAt(i);
+                char shortcutChar = (char) mRoleShortcuts.keyAt(i);
+                Intent intent = getRoleLaunchIntent(role);
+                ipw.println(shortcutChar + " " + role + " " + intent);
             }
 
-            for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
-                char shortcutChar = (char) mShiftComponentShortcuts.keyAt(i);
-                ComponentName component = mShiftComponentShortcuts.valueAt(i);
-                Intent intent = resolveComponentNameIntent(component);
-                ipw.println("SHIFT+" + shortcutChar + " " + component + " " + intent);
+            for (int i = 0; i < mShiftRoleShortcuts.size(); i++) {
+                String role = mShiftRoleShortcuts.valueAt(i);
+                char shortcutChar = (char) mShiftRoleShortcuts.keyAt(i);
+                Intent intent = getRoleLaunchIntent(role);
+                ipw.println("SHIFT+" + shortcutChar + " " + role + " " + intent);
             }
+
+            ipw.decreaseIndent();
+            ipw.println("Selectors");
+            ipw.increaseIndent();
+            for (int i = 0; i < mCategoryShortcuts.size(); i++) {
+                char shortcutChar = (char) mCategoryShortcuts.keyAt(i);
+                Intent intent = mCategoryShortcuts.valueAt(i);
+                ipw.println(shortcutChar + " " + intent);
+            }
+
+            for (int i = 0; i < mShiftCategoryShortcuts.size(); i++) {
+                char shortcutChar = (char) mShiftCategoryShortcuts.keyAt(i);
+                Intent intent = mShiftCategoryShortcuts.valueAt(i);
+                ipw.println("SHIFT+" + shortcutChar + " " + intent);
+
+            }
+
+            if (modifierShortcutManagerMultiuser()) {
+                ipw.decreaseIndent();
+                ipw.println("ComponentNames");
+                ipw.increaseIndent();
+                for (int i = 0; i < mComponentShortcuts.size(); i++) {
+                    char shortcutChar = (char) mComponentShortcuts.keyAt(i);
+                    ComponentName component = mComponentShortcuts.valueAt(i);
+                    Intent intent = resolveComponentNameIntent(component);
+                    ipw.println(shortcutChar + " " + component + " " + intent);
+                }
+
+                for (int i = 0; i < mShiftComponentShortcuts.size(); i++) {
+                    char shortcutChar = (char) mShiftComponentShortcuts.keyAt(i);
+                    ComponentName component = mShiftComponentShortcuts.valueAt(i);
+                    Intent intent = resolveComponentNameIntent(component);
+                    ipw.println("SHIFT+" + shortcutChar + " " + component + " " + intent);
+                }
+            }
+        }
+    }
+
+    private abstract static  class Bookmark {
+        private final char mShortcutChar;
+        private final boolean mShift;
+        protected Intent mIntent;
+
+        Bookmark(char shortcutChar, boolean shift) {
+            mShortcutChar = shortcutChar;
+            mShift = shift;
+        }
+
+        public char getShortcutChar() {
+            return mShortcutChar;
+        }
+
+        public boolean isShift() {
+            return mShift;
+        }
+
+        public abstract Intent getIntent(Context context);
+
+        public void clearIntent() {
+            mIntent = null;
+        }
+
+    }
+
+    private static final class RoleBookmark extends Bookmark {
+        private final String mRole;
+
+        RoleBookmark(char shortcutChar, boolean shift, String role) {
+            super(shortcutChar, shift);
+            mRole = role;
+        }
+
+        public String getRole() {
+            return mRole;
+        }
+
+        @Nullable
+        @Override
+        public Intent getIntent(Context context) {
+            if (mIntent != null) {
+                return mIntent;
+            }
+            mIntent = getRoleLaunchIntent(context, mRole);
+            return mIntent;
+        }
+    }
+
+    private static final class CategoryBookmark extends Bookmark {
+        private final String mCategory;
+
+        CategoryBookmark(char shortcutChar, boolean shift, String category) {
+            super(shortcutChar, shift);
+            mCategory = category;
+        }
+
+        @NonNull
+        @Override
+        public Intent getIntent(Context context) {
+            if (mIntent != null) {
+                return mIntent;
+            }
+
+            mIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, mCategory);
+            return mIntent;
+        }
+    }
+
+    private static final class ComponentBookmark extends Bookmark {
+        private final String mPackageName;
+        private final String mClassName;
+
+        ComponentBookmark(
+                char shortcutChar, boolean shift, String packageName, String className) {
+            super(shortcutChar, shift);
+            mPackageName = packageName;
+            mClassName = className;
+        }
+
+        @Nullable
+        @Override
+        public Intent getIntent(Context context) {
+            if (mIntent != null) {
+                return mIntent;
+            }
+            mIntent = resolveComponentNameIntent(context, mPackageName, mClassName);
+            return mIntent;
         }
     }
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ba3de33..749025b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.policy;
 
+import static android.Manifest.permission.CREATE_VIRTUAL_DEVICE;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
 import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
@@ -59,13 +60,18 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD;
@@ -1076,16 +1082,6 @@
     private void interceptPowerKeyUp(KeyEvent event, boolean canceled) {
         // Inform the StatusBar; but do not allow it to consume the event.
         sendSystemKeyToStatusBarAsync(event);
-
-        final boolean handled = canceled || mPowerKeyHandled;
-
-        if (!handled) {
-            if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == 0) {
-                // Abort possibly stuck animations only when power key up without long press case.
-                mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe);
-            }
-        }
-
         finishPowerKeyPress();
     }
 
@@ -3068,7 +3064,7 @@
     /** {@inheritDoc} */
     @Override
     public int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
-            int[] outAppOp) {
+            int[] outAppOp, int displayId) {
         if (isRoundedCornerOverlay && mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
                 != PERMISSION_GRANTED) {
             return ADD_PERMISSION_DENIED;
@@ -3108,6 +3104,12 @@
                 case TYPE_VOICE_INTERACTION:
                 case TYPE_QS_DIALOG:
                 case TYPE_NAVIGATION_BAR_PANEL:
+                case TYPE_STATUS_BAR:
+                case TYPE_NOTIFICATION_SHADE:
+                case TYPE_NAVIGATION_BAR:
+                case TYPE_STATUS_BAR_ADDITIONAL:
+                case TYPE_STATUS_BAR_SUB_PANEL:
+                case TYPE_VOICE_INTERACTION_STARTING:
                     // The window manager will check these.
                     return ADD_OKAY;
             }
@@ -3151,6 +3153,13 @@
             return ADD_OKAY;
         }
 
+        // Allow virtual device owners to add overlays on the displays they own.
+        if (mWindowManagerFuncs.isCallerVirtualDeviceOwner(displayId, callingUid)
+                && mContext.checkCallingOrSelfPermission(CREATE_VIRTUAL_DEVICE)
+                == PERMISSION_GRANTED) {
+            return ADD_OKAY;
+        }
+
         // check if user has enabled this operation. SecurityException will be thrown if this app
         // has not been allowed by the user. The reason to use "noteOp" (instead of checkOp) is to
         // make sure the usage is logged.
@@ -3591,18 +3600,6 @@
                 if (down) {
                     int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1;
 
-                    // Disable autobrightness if it's on
-                    int auto = Settings.System.getIntForUser(
-                            mContext.getContentResolver(),
-                            Settings.System.SCREEN_BRIGHTNESS_MODE,
-                            Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
-                            UserHandle.USER_CURRENT_OR_SELF);
-                    if (auto != 0) {
-                        Settings.System.putIntForUser(mContext.getContentResolver(),
-                                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
-                                UserHandle.USER_CURRENT_OR_SELF);
-                    }
                     int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
 
                     float minLinearBrightness = mPowerManager.getBrightnessConstraint(
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/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 67f5f27..892af6b 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -313,12 +313,6 @@
         }
 
         /**
-         * Hint to window manager that the user has started a navigation action that should
-         * abort animations that have no timeout, in case they got stuck.
-         */
-        void triggerAnimationFailsafe();
-
-        /**
          * The keyguard showing state has changed
          */
         void onKeyguardShowingAndNotOccludedChanged();
@@ -368,6 +362,12 @@
          * Invoked when a screenshot is taken of the given display to notify registered listeners.
          */
         List<ComponentName> notifyScreenshotListeners(int displayId);
+
+        /**
+         * Returns whether the given UID is the owner of a virtual device, which the given display
+         * belongs to.
+         */
+        boolean isCallerVirtualDeviceOwner(int displayId, int callingUid);
     }
 
     /**
@@ -427,6 +427,7 @@
      * @param packageName package name
      * @param outAppOp First element will be filled with the app op corresponding to
      *                 this window, or OP_NONE.
+     * @param displayId The display on which this window is being added.
      *
      * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
      *      else an error code, usually
@@ -435,7 +436,7 @@
      * @see WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY
      */
     int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
-            int[] outAppOp);
+            int[] outAppOp, int displayId);
 
     /**
      * After the window manager has computed the current configuration based
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/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 1a2a196..303828f 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -1064,9 +1064,9 @@
     private void notifyWakeLockListener(IWakeLockCallback callback, String tag, boolean isEnabled,
             int ownerUid, int ownerPid, int flags, WorkSource workSource, String packageName,
             String historyTag) {
+        long currentTime = mInjector.currentTimeMillis();
         mHandler.post(() -> {
             if (mFlags.improveWakelockLatency()) {
-                long currentTime = mInjector.currentTimeMillis();
                 if (isEnabled) {
                     notifyWakelockAcquisition(tag, ownerUid, ownerPid, flags,
                             workSource, packageName, historyTag, currentTime);
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..6847a5c 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -54,6 +54,7 @@
 import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.Temperature;
+import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.Slog;
@@ -247,6 +248,7 @@
 
     private void setStatusLocked(int newStatus) {
         if (newStatus != mStatus) {
+            Trace.traceCounter(Trace.TRACE_TAG_POWER, "ThermalManagerService.status", newStatus);
             mStatus = newStatus;
             notifyStatusListenersLocked();
         }
@@ -1644,8 +1646,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/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java
index 968ff59..eda222e 100644
--- a/services/core/java/com/android/server/power/WakeLockLog.java
+++ b/services/core/java/com/android/server/power/WakeLockLog.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.PowerManager;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -122,6 +123,9 @@
 
     private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
+    @VisibleForTesting
+    static final String SYSTEM_PACKAGE_NAME = "System";
+
     /**
      * Lock protects WakeLockLog.dump (binder thread) from conflicting with changes to the log
      * happening on the background thread.
@@ -516,21 +520,26 @@
                 return;
             }
 
-            String[] packages;
-            if (uidToPackagesCache.contains(tag.ownerUid)) {
-                packages = uidToPackagesCache.get(tag.ownerUid);
-            } else {
-                packages = packageManager.getPackagesForUid(tag.ownerUid);
-                uidToPackagesCache.put(tag.ownerUid, packages);
+            if (tag.ownerUid == Process.SYSTEM_UID) {
+                packageName = SYSTEM_PACKAGE_NAME;
             }
+            else {
+                String[] packages;
+                if (uidToPackagesCache.contains(tag.ownerUid)) {
+                    packages = uidToPackagesCache.get(tag.ownerUid);
+                } else {
+                    packages = packageManager.getPackagesForUid(tag.ownerUid);
+                    uidToPackagesCache.put(tag.ownerUid, packages);
+                }
 
-            if (packages != null && packages.length > 0) {
-                packageName = packages[0];
-                if (packages.length > 1) {
-                    StringBuilder sb = new StringBuilder();
-                    sb.append(packageName)
-                            .append(",...");
-                    packageName = sb.toString();
+                if (packages != null && packages.length > 0) {
+                    packageName = packages[0];
+                    if (packages.length > 1) {
+                        StringBuilder sb = new StringBuilder();
+                        sb.append(packageName)
+                                .append(",...");
+                        packageName = sb.toString();
+                    }
                 }
             }
         }
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/AggregatedPowerStatsSection.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
deleted file mode 100644
index 7ba4330..0000000
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java
+++ /dev/null
@@ -1,48 +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.server.power.stats;
-
-import android.util.IndentingPrintWriter;
-
-import com.android.modules.utils.TypedXmlSerializer;
-
-import java.io.IOException;
-
-class AggregatedPowerStatsSection extends PowerStatsSpan.Section {
-    public static final String TYPE = "aggregated-power-stats";
-
-    private final AggregatedPowerStats mAggregatedPowerStats;
-
-    AggregatedPowerStatsSection(AggregatedPowerStats aggregatedPowerStats) {
-        super(TYPE);
-        mAggregatedPowerStats = aggregatedPowerStats;
-    }
-
-    public AggregatedPowerStats getAggregatedPowerStats() {
-        return mAggregatedPowerStats;
-    }
-
-    @Override
-    void write(TypedXmlSerializer serializer) throws IOException {
-        mAggregatedPowerStats.writeXml(serializer);
-    }
-
-    @Override
-    public void dump(IndentingPrintWriter ipw) {
-        mAggregatedPowerStats.dump(ipw);
-    }
-}
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 2f16419..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;
@@ -149,8 +149,7 @@
     // WiFi keeps an accumulated total of stats. Keep the last WiFi stats so we can compute a delta.
     // (This is unlike Bluetooth, where BatteryStatsImpl is left responsible for taking the delta.)
     @GuardedBy("mWorkerLock")
-    private WifiActivityEnergyInfo mLastWifiInfo =
-            new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
+    private WifiActivityEnergyInfo mLastWifiInfo = null;
 
     /**
      * Maps an {@link EnergyConsumerType} to it's corresponding {@link EnergyConsumer#id}s,
@@ -244,6 +243,7 @@
             }
             synchronized (mStats) {
                 mStats.initEnergyConsumerStatsLocked(supportedStdBuckets, customBucketNames);
+                mPerDisplayScreenStates = new int[mStats.getDisplayCount()];
             }
         }
     }
@@ -491,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");
                         }
@@ -768,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 =
@@ -791,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();
-        }
     }
 
     /**
@@ -827,8 +827,18 @@
         return null;
     }
 
+    /**
+     * Return a delta WifiActivityEnergyInfo from the last WifiActivityEnergyInfo passed to the
+     * method.
+     */
+    @VisibleForTesting
     @GuardedBy("mWorkerLock")
-    private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
+    public WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) {
+        if (mLastWifiInfo == null) {
+            // This is the first time WifiActivityEnergyInfo has been collected since system boot.
+            // Use this first WifiActivityEnergyInfo as the starting point for all accumulations.
+            mLastWifiInfo = latest;
+        }
         final long timePeriodMs = latest.getTimeSinceBootMillis()
                 - mLastWifiInfo.getTimeSinceBootMillis();
         final long lastScanMs = mLastWifiInfo.getControllerScanDurationMillis();
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 1d3de57..680b1ac 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -23,8 +23,6 @@
 import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
 import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
 
-import static com.android.server.power.stats.MobileRadioPowerStatsCollector.mapRadioAccessNetworkTypeToRadioAccessTechnology;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -149,6 +147,7 @@
 import com.android.server.LocalServices;
 import com.android.server.power.optimization.Flags;
 import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
+import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
 
 import libcore.util.EmptyArray;
 
@@ -180,7 +179,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.IntSupplier;
 import java.util.function.LongSupplier;
 import java.util.function.Supplier;
 
@@ -1167,7 +1165,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];
@@ -2029,7 +2026,7 @@
         void setContext(Context context) {
             mPackageManager = context.getPackageManager();
             mConsumedEnergyRetriever = new PowerStatsCollector.ConsumedEnergyRetrieverImpl(
-                    LocalServices.getService(PowerStatsInternal.class));
+                    LocalServices.getService(PowerStatsInternal.class), () -> mBatteryVoltageMv);
             mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class);
             mTelephonyManager =
                     (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
@@ -2084,11 +2081,6 @@
         }
 
         @Override
-        public IntSupplier getVoltageSupplier() {
-            return () -> mBatteryVoltageMv;
-        }
-
-        @Override
         public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
             return mScreenUsageTimeRetriever;
         }
@@ -5528,7 +5520,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)) {
@@ -13192,7 +13184,8 @@
                 final int freq = deltaInfo.getSpecificInfoFrequencyRange(index);
 
                 // Map RadioAccessNetworkType to course grain RadioAccessTechnology.
-                final int ratBucket = mapRadioAccessNetworkTypeToRadioAccessTechnology(rat);
+                final int ratBucket = MobileRadioPowerStatsLayout
+                        .mapRadioAccessNetworkTypeToRadioAccessTechnology(rat);
                 final RadioAccessTechnologyBatteryStats ratStats = getRatBatteryStatsLocked(
                         ratBucket);
 
@@ -15301,15 +15294,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;
@@ -15487,7 +15471,8 @@
         final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which);
         final long totalControllerActivityTimeMs =
                 computeBatteryRealtime(mClock.elapsedRealtime() * 1000, which) / 1000;
-        final long sleepTimeMs = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs);
+        final long sleepTimeMs = Math.max(0,
+                totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs));
         final long energyConsumedMaMs = counter.getPowerCounter().getCountLocked(which);
         final long monitoredRailChargeConsumedMaMs =
                 counter.getMonitoredRailChargeConsumedMaMs().getCountLocked(which);
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index d51cfea..87a3e5e 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.power.stats;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.SensorManager;
 import android.os.BatteryConsumer;
@@ -27,7 +28,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.CpuScalingPolicies;
@@ -44,8 +44,7 @@
 public class BatteryUsageStatsProvider {
     private static final String TAG = "BatteryUsageStatsProv";
     private final Context mContext;
-    private final SparseBooleanArray mPowerStatsExporterEnabled = new SparseBooleanArray();
-    private final PowerStatsExporter mPowerStatsExporter;
+    private final PowerAttributor mPowerAttributor;
     private final PowerStatsStore mPowerStatsStore;
     private final PowerProfile mPowerProfile;
     private final CpuScalingPolicies mCpuScalingPolicies;
@@ -53,16 +52,18 @@
     private final Object mLock = new Object();
     private List<PowerCalculator> mPowerCalculators;
 
-    public BatteryUsageStatsProvider(Context context,
-            PowerStatsExporter powerStatsExporter,
-            PowerProfile powerProfile, CpuScalingPolicies cpuScalingPolicies,
-            PowerStatsStore powerStatsStore, Clock clock) {
+    public BatteryUsageStatsProvider(@NonNull Context context,
+            @NonNull PowerAttributor powerAttributor,
+            @NonNull PowerProfile powerProfile, @NonNull CpuScalingPolicies cpuScalingPolicies,
+            @NonNull PowerStatsStore powerStatsStore, @NonNull Clock clock) {
         mContext = context;
-        mPowerStatsExporter = powerStatsExporter;
+        mPowerAttributor = powerAttributor;
         mPowerStatsStore = powerStatsStore;
         mPowerProfile = powerProfile;
         mCpuScalingPolicies = cpuScalingPolicies;
         mClock = clock;
+
+        mPowerStatsStore.addSectionReader(new BatteryUsageStatsSection.Reader());
     }
 
     private List<PowerCalculator> getPowerCalculators() {
@@ -72,55 +73,67 @@
 
                 // Power calculators are applied in the order of registration
                 mPowerCalculators.add(new BatteryChargeCalculator());
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_CPU)) {
                     mPowerCalculators.add(
                             new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile));
                 }
                 mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
                 if (!BatteryStats.checkWifiOnly(mContext)) {
-                    if (!mPowerStatsExporterEnabled.get(
+                    if (!mPowerAttributor.isPowerComponentSupported(
                             BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)) {
                         mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile));
                     }
-                    if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_PHONE)) {
+                    if (!mPowerAttributor.isPowerComponentSupported(
+                            BatteryConsumer.POWER_COMPONENT_PHONE)) {
                         mPowerCalculators.add(new PhonePowerCalculator(mPowerProfile));
                     }
                 }
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_WIFI)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_WIFI)) {
                     mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile));
                 }
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) {
                     mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile));
                 }
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_SENSORS)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_SENSORS)) {
                     mPowerCalculators.add(new SensorPowerCalculator(
                             mContext.getSystemService(SensorManager.class)));
                 }
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_GNSS)) {
                     mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile));
                 }
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_CAMERA)) {
                     mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile));
                 }
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) {
                     mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile));
                 }
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_AUDIO)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_AUDIO)) {
                     mPowerCalculators.add(new AudioPowerCalculator(mPowerProfile));
                 }
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_VIDEO)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_VIDEO)) {
                     mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile));
                 }
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_SCREEN)) {
                     mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
                 }
-                if (!mPowerStatsExporterEnabled.get(
+                if (!mPowerAttributor.isPowerComponentSupported(
                         BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) {
                     mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
                 }
                 mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
-                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_ANY)) {
+                if (!mPowerAttributor.isPowerComponentSupported(
+                        BatteryConsumer.POWER_COMPONENT_ANY)) {
                     mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile));
                 }
                 mPowerCalculators.add(new UserPowerCalculator());
@@ -196,7 +209,7 @@
         final boolean includeProcessStateData = ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
                 && stats.isProcessStateDataAvailable();
-        final boolean includeVirtualUids =  ((query.getFlags()
+        final boolean includeVirtualUids = ((query.getFlags()
                 & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS) != 0);
         final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
 
@@ -258,10 +271,8 @@
             }
         }
 
-        if (mPowerStatsExporterEnabled.indexOfValue(true) >= 0) {
-            mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder,
-                    monotonicStartTime, monotonicEndTime);
-        }
+        mPowerAttributor.estimatePowerConsumption(batteryUsageStatsBuilder, stats.getHistory(),
+                monotonicStartTime, monotonicEndTime);
 
         BatteryUsageStats batteryUsageStats = batteryUsageStatsBuilder.build();
         if (includeProcessStateData) {
@@ -272,6 +283,7 @@
 
     // STOPSHIP(b/229906525): remove verification before shipping
     private static boolean sErrorReported;
+
     private void verify(BatteryUsageStats stats) {
         if (sErrorReported) {
             return;
@@ -390,7 +402,7 @@
             // while the "to" timestamp is *inclusive*.
             boolean isInRange =
                     (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp())
-                    && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp());
+                            && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp());
             if (!isInRange) {
                 continue;
             }
@@ -422,12 +434,4 @@
         }
         return builder.build();
     }
-
-    /**
-     * Specify whether PowerStats based attribution is supported for the specified component.
-     */
-    public void setPowerStatsExporterEnabled(int powerComponentId, boolean enabled) {
-        mPowerStatsExporterEnabled.put(powerComponentId, enabled);
-        mPowerStatsExporter.setPowerComponentEnabled(powerComponentId, enabled);
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
index b95faac..af36524 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java
@@ -19,8 +19,11 @@
 import android.os.BatteryUsageStats;
 import android.util.IndentingPrintWriter;
 
+import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 
 class BatteryUsageStatsSection extends PowerStatsSpan.Section {
@@ -38,7 +41,7 @@
     }
 
     @Override
-    void write(TypedXmlSerializer serializer) throws IOException {
+    public void write(TypedXmlSerializer serializer) throws IOException {
         mBatteryUsageStats.writeXml(serializer);
     }
 
@@ -46,4 +49,17 @@
     public void dump(IndentingPrintWriter ipw) {
         mBatteryUsageStats.dump(ipw, "");
     }
+
+    static class Reader implements PowerStatsSpan.SectionReader {
+        @Override
+        public String getType() {
+            return TYPE;
+        }
+
+        @Override
+        public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return new BatteryUsageStatsSection(BatteryUsageStats.createFromXml(parser));
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java
index 8a5085b..539c415 100644
--- a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java
@@ -28,13 +28,12 @@
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.BluetoothPowerStatsLayout;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
-import java.util.function.IntSupplier;
 
 public class BluetoothPowerStatsCollector extends PowerStatsCollector {
     private static final String TAG = "BluetoothPowerStatsCollector";
@@ -43,7 +42,7 @@
 
     private static final long ENERGY_UNSPECIFIED = -1;
 
-    interface BluetoothStatsRetriever {
+    public interface BluetoothStatsRetriever {
         interface Callback {
             void onBluetoothScanTime(int uid, long scanTimeMs);
         }
@@ -54,29 +53,25 @@
                 BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback);
     }
 
-    interface Injector {
+    public interface Injector {
         Handler getHandler();
         Clock getClock();
         PowerStatsUidResolver getUidResolver();
         long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
         PackageManager getPackageManager();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
-        IntSupplier getVoltageSupplier();
         BluetoothStatsRetriever getBluetoothStatsRetriever();
     }
 
     private final Injector mInjector;
 
-    private BluetoothPowerStatsLayout mLayout;
+    private com.android.server.power.stats.format.BluetoothPowerStatsLayout mLayout;
     private boolean mIsInitialized;
     private PowerStats mPowerStats;
     private long[] mDeviceStats;
     private BluetoothStatsRetriever mBluetoothStatsRetriever;
     private ConsumedEnergyRetriever mConsumedEnergyRetriever;
-    private IntSupplier mVoltageSupplier;
-    private int[] mEnergyConsumerIds = new int[0];
-    private long[] mLastConsumedEnergyUws;
-    private int mLastVoltageMv;
+    private ConsumedEnergyHelper mConsumedEnergyHelper;
 
     private long mLastRxTime;
     private long mLastTxTime;
@@ -93,7 +88,7 @@
 
     private final SparseArray<UidStats> mUidStats = new SparseArray<>();
 
-    BluetoothPowerStatsCollector(Injector injector) {
+    public BluetoothPowerStatsCollector(Injector injector) {
         super(injector.getHandler(),  injector.getPowerStatsCollectionThrottlePeriod(
                         BatteryConsumer.powerComponentIdToString(
                                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH)),
@@ -122,21 +117,11 @@
             return false;
         }
 
-        mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
-        mVoltageSupplier = mInjector.getVoltageSupplier();
         mBluetoothStatsRetriever = mInjector.getBluetoothStatsRetriever();
-        mEnergyConsumerIds =
-                mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH);
-        mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
-        Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
-
-        mLayout = new BluetoothPowerStatsLayout();
-        mLayout.addDeviceBluetoothControllerActivity();
-        mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length);
-        mLayout.addDeviceSectionUsageDuration();
-        mLayout.addDeviceSectionPowerEstimate();
-        mLayout.addUidTrafficStats();
-        mLayout.addUidSectionPowerEstimate();
+        mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
+        mConsumedEnergyHelper = new ConsumedEnergyHelper(mConsumedEnergyRetriever,
+                EnergyConsumerType.BLUETOOTH);
+        mLayout = new BluetoothPowerStatsLayout(mConsumedEnergyHelper.getEnergyConsumerCount());
 
         PersistableBundle extras = new PersistableBundle();
         mLayout.toExtras(extras);
@@ -152,7 +137,7 @@
     }
 
     @Override
-    protected PowerStats collectStats() {
+    public PowerStats collectStats() {
         if (!ensureInitialized()) {
             return null;
         }
@@ -162,9 +147,7 @@
         collectBluetoothActivityInfo();
         collectBluetoothScanStats();
 
-        if (mEnergyConsumerIds.length != 0) {
-            collectEnergyConsumers();
-        }
+        mConsumedEnergyHelper.collectConsumedEnergy(mPowerStats, mLayout);
 
         return mPowerStats;
     }
@@ -296,34 +279,6 @@
         mLayout.setDeviceScanTime(mDeviceStats, totalScanTime);
     }
 
-    private void collectEnergyConsumers() {
-        int voltageMv = mVoltageSupplier.getAsInt();
-        if (voltageMv <= 0) {
-            Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
-                    + " mV) when querying energy consumers");
-            return;
-        }
-
-        int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
-        mLastVoltageMv = voltageMv;
-
-        long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
-        if (energyUws == null) {
-            return;
-        }
-
-        for (int i = energyUws.length - 1; i >= 0; i--) {
-            long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
-                    ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
-            if (energyDelta < 0) {
-                // Likely, restart of powerstats HAL
-                energyDelta = 0;
-            }
-            mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
-            mLastConsumedEnergyUws[i] = energyUws[i];
-        }
-    }
-
     @Override
     protected void onUidRemoved(int uid) {
         super.onUidRemoved(uid);
diff --git a/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java
index 8705bd5..0f70064 100644
--- a/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java
@@ -19,12 +19,13 @@
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 
+import com.android.server.power.stats.format.BinaryStatePowerStatsLayout;
+
 public class CameraPowerStatsCollector extends EnergyConsumerPowerStatsCollector {
 
-    CameraPowerStatsCollector(Injector injector) {
+    public CameraPowerStatsCollector(Injector injector) {
         super(injector, BatteryConsumer.POWER_COMPONENT_CAMERA,
                 BatteryConsumer.powerComponentIdToString(BatteryConsumer.POWER_COMPONENT_CAMERA),
-                EnergyConsumerType.CAMERA, /* energy consumer name */ null,
-                new BinaryStatePowerStatsLayout());
+                EnergyConsumerType.CAMERA, new BinaryStatePowerStatsLayout());
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index b5ef67b..128f14a 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -31,11 +31,10 @@
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.CpuPowerStatsLayout;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
 import java.util.Locale;
-import java.util.function.IntSupplier;
 
 /**
  * Collects snapshots of power-related system statistics.
@@ -46,7 +45,6 @@
 public class CpuPowerStatsCollector extends PowerStatsCollector {
     private static final String TAG = "CpuPowerStatsCollector";
     private static final long NANOS_PER_MILLIS = 1000000;
-    private static final long ENERGY_UNSPECIFIED = -1;
     private static final int DEFAULT_CPU_POWER_BRACKETS = 3;
     private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2;
 
@@ -58,7 +56,6 @@
         PowerProfile getPowerProfile();
         KernelCpuStatsReader getKernelCpuStatsReader();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
-        IntSupplier getVoltageSupplier();
         long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
 
         default int getDefaultCpuPowerBrackets() {
@@ -76,8 +73,7 @@
     private CpuScalingPolicies mCpuScalingPolicies;
     private PowerProfile mPowerProfile;
     private KernelCpuStatsReader mKernelCpuStatsReader;
-    private ConsumedEnergyRetriever mConsumedEnergyRetriever;
-    private IntSupplier mVoltageSupplier;
+    private ConsumedEnergyHelper mConsumedEnergyHelper;
     private int mDefaultCpuPowerBrackets;
     private int mDefaultCpuPowerBracketsPerEnergyConsumer;
     private long[] mCpuTimeByScalingStep;
@@ -85,15 +81,12 @@
     private long[] mTempUidStats;
     private final SparseArray<UidStats> mUidStats = new SparseArray<>();
     private boolean mIsPerUidTimeInStateSupported;
-    private int[] mCpuEnergyConsumerIds = new int[0];
     private PowerStats.Descriptor mPowerStatsDescriptor;
     // Reusable instance
     private PowerStats mCpuPowerStats;
     private CpuPowerStatsLayout mLayout;
     private long mLastUpdateTimestampNanos;
     private long mLastUpdateUptimeMillis;
-    private int mLastVoltageMv;
-    private long[] mLastConsumedEnergyUws;
 
     CpuPowerStatsCollector(Injector injector) {
         super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
@@ -115,31 +108,22 @@
         mCpuScalingPolicies = mInjector.getCpuScalingPolicies();
         mPowerProfile = mInjector.getPowerProfile();
         mKernelCpuStatsReader = mInjector.getKernelCpuStatsReader();
-        mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
-        mVoltageSupplier = mInjector.getVoltageSupplier();
         mDefaultCpuPowerBrackets = mInjector.getDefaultCpuPowerBrackets();
         mDefaultCpuPowerBracketsPerEnergyConsumer =
                 mInjector.getDefaultCpuPowerBracketsPerEnergyConsumer();
 
         mIsPerUidTimeInStateSupported = mKernelCpuStatsReader.isSupportedFeature();
-        mCpuEnergyConsumerIds =
-                mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.CPU_CLUSTER);
-        mLastConsumedEnergyUws = new long[mCpuEnergyConsumerIds.length];
-        Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
+
+        mConsumedEnergyHelper = new ConsumedEnergyHelper(mInjector.getConsumedEnergyRetriever(),
+                EnergyConsumerType.CPU_CLUSTER);
 
         int cpuScalingStepCount = mCpuScalingPolicies.getScalingStepCount();
         mCpuTimeByScalingStep = new long[cpuScalingStepCount];
         mTempCpuTimeByScalingStep = new long[cpuScalingStepCount];
         int[] scalingStepToPowerBracketMap = initPowerBrackets();
 
-        mLayout = new CpuPowerStatsLayout();
-        mLayout.addDeviceSectionCpuTimeByScalingStep(cpuScalingStepCount);
-        mLayout.addDeviceSectionCpuTimeByCluster(mCpuScalingPolicies.getPolicies().length);
-        mLayout.addDeviceSectionUsageDuration();
-        mLayout.addDeviceSectionEnergyConsumers(mCpuEnergyConsumerIds.length);
-        mLayout.addDeviceSectionPowerEstimate();
-        mLayout.addUidSectionCpuTimeByPowerBracket(scalingStepToPowerBracketMap);
-        mLayout.addUidSectionPowerEstimate();
+        mLayout = new CpuPowerStatsLayout(mConsumedEnergyHelper.getEnergyConsumerCount(),
+                mCpuScalingPolicies.getPolicies().length, scalingStepToPowerBracketMap);
 
         PersistableBundle extras = new PersistableBundle();
         mLayout.toExtras(extras);
@@ -158,16 +142,18 @@
     private int[] initPowerBrackets() {
         if (mPowerProfile.getCpuPowerBracketCount() != PowerProfile.POWER_BRACKETS_UNSPECIFIED) {
             return initPowerBracketsFromPowerProfile();
-        } else if (mCpuEnergyConsumerIds.length == 0 || mCpuEnergyConsumerIds.length == 1) {
+        } else if (mConsumedEnergyHelper.getEnergyConsumerCount() == 0
+                || mConsumedEnergyHelper.getEnergyConsumerCount() == 1) {
             return initDefaultPowerBrackets(mDefaultCpuPowerBrackets);
-        } else if (mCpuScalingPolicies.getPolicies().length == mCpuEnergyConsumerIds.length) {
+        } else if (mCpuScalingPolicies.getPolicies().length
+                == mConsumedEnergyHelper.getEnergyConsumerCount()) {
             return initPowerBracketsByCluster(mDefaultCpuPowerBracketsPerEnergyConsumer);
         } else {
             Slog.i(TAG, "Assigning a single power brackets to each CPU_CLUSTER energy consumer."
                         + " Number of CPU clusters ("
                         + mCpuScalingPolicies.getPolicies().length
                         + ") does not match the number of energy consumers ("
-                        + mCpuEnergyConsumerIds.length + "). "
+                        + mConsumedEnergyHelper.getEnergyConsumerCount() + "). "
                         + " Using default power bucket assignment.");
             return initDefaultPowerBrackets(mDefaultCpuPowerBrackets);
         }
@@ -368,41 +354,11 @@
         }
         mLayout.setUsageDuration(mCpuPowerStats.stats, uptimeDelta);
 
-        if (mCpuEnergyConsumerIds.length != 0) {
-            collectEnergyConsumers();
-        }
+        mConsumedEnergyHelper.collectConsumedEnergy(mCpuPowerStats, mLayout);
 
         return mCpuPowerStats;
     }
 
-    private void collectEnergyConsumers() {
-        int voltageMv = mVoltageSupplier.getAsInt();
-        if (voltageMv <= 0) {
-            Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
-                          + " mV) when querying energy consumers");
-            return;
-        }
-
-        int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
-        mLastVoltageMv = voltageMv;
-
-        long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mCpuEnergyConsumerIds);
-        if (energyUws == null) {
-            return;
-        }
-
-        for (int i = energyUws.length - 1; i >= 0; i--) {
-            long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
-                    ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
-            if (energyDelta < 0) {
-                // Likely, restart of powerstats HAL
-                energyDelta = 0;
-            }
-            mLayout.setConsumedEnergy(mCpuPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
-            mLastConsumedEnergyUws[i] = energyUws[i];
-        }
-    }
-
     @VisibleForNative
     interface KernelCpuStatsCallback {
         @Keep // Called from native
diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
index 4bfe442..c1c7e12 100644
--- a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java
@@ -19,6 +19,8 @@
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 
+import com.android.server.power.stats.format.EnergyConsumerPowerStatsLayout;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -29,7 +31,8 @@
     private final EnergyConsumerPowerStatsCollector.Injector mInjector;
     private List<EnergyConsumerPowerStatsCollector> mCollectors;
 
-    CustomEnergyConsumerPowerStatsCollector(EnergyConsumerPowerStatsCollector.Injector injector) {
+    public CustomEnergyConsumerPowerStatsCollector(
+            EnergyConsumerPowerStatsCollector.Injector injector) {
         super(injector.getHandler(), 0, injector.getUidResolver(), injector.getClock());
         mInjector = injector;
     }
@@ -46,7 +49,8 @@
         for (int i = 0; i < energyConsumerIds.length; i++) {
             String name = retriever.getEnergyConsumerName(energyConsumerIds[i]);
             EnergyConsumerPowerStatsCollector collector = new EnergyConsumerPowerStatsCollector(
-                    mInjector, powerComponentId++, name, energyConsumerIds[i], sLayout);
+                    mInjector, powerComponentId++, name, EnergyConsumerType.OTHER,
+                    energyConsumerIds[i], sLayout);
             collector.setEnabled(true);
             collector.addConsumer(this::deliverStats);
             mCollectors.add(collector);
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
index 79fbe8e..1d2e388 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
@@ -16,78 +16,57 @@
 
 package com.android.server.power.stats;
 
-import android.hardware.power.stats.EnergyConsumerAttribution;
-import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.Handler;
 import android.os.PersistableBundle;
-import android.util.Slog;
-import android.util.SparseLongArray;
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
-
-import java.util.function.IntSupplier;
+import com.android.server.power.stats.format.EnergyConsumerPowerStatsLayout;
 
 public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector {
-    private static final String TAG = "EnergyConsumerPowerStatsCollector";
 
-    private static final long ENERGY_UNSPECIFIED = -1;
-
-    interface Injector {
+    public interface Injector {
         Handler getHandler();
         Clock getClock();
         PowerStatsUidResolver getUidResolver();
         long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
-        IntSupplier getVoltageSupplier();
     }
 
+    private static final int UNSPECIFIED = -1;
+
     private final Injector mInjector;
     private final int mPowerComponentId;
     private final String mPowerComponentName;
+    private final int mEnergyConsumerId;
     private final int mEnergyConsumerType;
-    private final String mEnergyConsumerName;
 
     private final EnergyConsumerPowerStatsLayout mLayout;
     private boolean mIsInitialized;
 
     private PowerStats mPowerStats;
-    private ConsumedEnergyRetriever mConsumedEnergyRetriever;
-    private IntSupplier mVoltageSupplier;
-    private int[] mEnergyConsumerIds;
-    private long mLastConsumedEnergyUws = ENERGY_UNSPECIFIED;
-    private SparseLongArray mLastConsumerEnergyPerUid = new SparseLongArray();
-    private int mLastVoltageMv;
+    private ConsumedEnergyHelper mConsumedEnergyHelper;
     private long mLastUpdateTimestamp;
-    private boolean mFirstCollection = true;
 
     EnergyConsumerPowerStatsCollector(Injector injector, int powerComponentId,
             String powerComponentName, @EnergyConsumerType int energyConsumerType,
-            String energyConsumerName, EnergyConsumerPowerStatsLayout statsLayout) {
-        super(injector.getHandler(),
-                injector.getPowerStatsCollectionThrottlePeriod(powerComponentName),
-                injector.getUidResolver(), injector.getClock());
-        mInjector = injector;
-        mPowerComponentId = powerComponentId;
-        mPowerComponentName = powerComponentName;
-        mEnergyConsumerType = energyConsumerType;
-        mEnergyConsumerName = energyConsumerName;
-        mLayout = statsLayout;
+            EnergyConsumerPowerStatsLayout statsLayout) {
+        this(injector, powerComponentId, powerComponentName, energyConsumerType, UNSPECIFIED,
+                statsLayout);
     }
 
     EnergyConsumerPowerStatsCollector(Injector injector, int powerComponentId,
-            String powerComponentName, int energyConsumerId,
-            EnergyConsumerPowerStatsLayout statsLayout) {
+            String powerComponentName, @EnergyConsumerType int energyConsumerType,
+            int energyConsumerId, EnergyConsumerPowerStatsLayout statsLayout) {
         super(injector.getHandler(),
                 injector.getPowerStatsCollectionThrottlePeriod(powerComponentName),
                 injector.getUidResolver(), injector.getClock());
         mInjector = injector;
         mPowerComponentId = powerComponentId;
         mPowerComponentName = powerComponentName;
-        mEnergyConsumerIds = new int[]{energyConsumerId};
-        mEnergyConsumerType = EnergyConsumerType.OTHER;
-        mEnergyConsumerName = null;
+        mEnergyConsumerId = energyConsumerId;
+        mEnergyConsumerType = energyConsumerType;
         mLayout = statsLayout;
     }
 
@@ -100,12 +79,14 @@
             return false;
         }
 
-        mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
-        mVoltageSupplier = mInjector.getVoltageSupplier();
-        if (mEnergyConsumerIds == null) {
-            mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(mEnergyConsumerType,
-                    mEnergyConsumerName);
+        if (mEnergyConsumerId != UNSPECIFIED) {
+            mConsumedEnergyHelper = new ConsumedEnergyHelper(mInjector.getConsumedEnergyRetriever(),
+                    mEnergyConsumerId, true /* perUidAttribution */);
+        } else {
+            mConsumedEnergyHelper = new ConsumedEnergyHelper(mInjector.getConsumedEnergyRetriever(),
+                    mEnergyConsumerType);
         }
+
         PersistableBundle extras = new PersistableBundle();
         mLayout.toExtras(extras);
         PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor(
@@ -124,85 +105,15 @@
             return null;
         }
 
-        if (mEnergyConsumerIds.length == 0) {
-            return null;
-        }
-
-        int voltageMv = mVoltageSupplier.getAsInt();
-        int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
-        if (averageVoltage <= 0) {
-            Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
-                    + " mV) when querying energy consumers");
-            return null;
-        }
-
-        mLastVoltageMv = voltageMv;
-
-        EnergyConsumerResult[] energy =
-                    mConsumedEnergyRetriever.getConsumedEnergy(mEnergyConsumerIds);
-        long consumedEnergy = 0;
-        if (energy != null) {
-            for (int i = energy.length - 1; i >= 0; i--) {
-                if (energy[i].energyUWs != ENERGY_UNSPECIFIED) {
-                    consumedEnergy += energy[i].energyUWs;
-                }
-            }
-        }
-
-        long energyDelta = mLastConsumedEnergyUws != ENERGY_UNSPECIFIED
-                ? consumedEnergy - mLastConsumedEnergyUws : 0;
-        mLastConsumedEnergyUws = consumedEnergy;
-        if (energyDelta < 0) {
-            // Likely, restart of powerstats HAL
-            energyDelta = 0;
-        }
-
-        if (energyDelta == 0 && !mFirstCollection) {
-            return null;
-        }
-
-        mLayout.setConsumedEnergy(mPowerStats.stats, 0, uJtoUc(energyDelta, averageVoltage));
-
         mPowerStats.uidStats.clear();
-        if (energy != null) {
-            for (int i = energy.length - 1; i >= 0; i--) {
-                EnergyConsumerAttribution[] perUid = energy[i].attribution;
-                if (perUid == null) {
-                    continue;
-                }
 
-                for (EnergyConsumerAttribution attribution : perUid) {
-                    int uid = mUidResolver.mapUid(attribution.uid);
-                    long lastEnergy = mLastConsumerEnergyPerUid.get(uid, ENERGY_UNSPECIFIED);
-                    mLastConsumerEnergyPerUid.put(uid, attribution.energyUWs);
-                    if (lastEnergy == ENERGY_UNSPECIFIED) {
-                        continue;
-                    }
-                    long deltaEnergy = attribution.energyUWs - lastEnergy;
-                    if (deltaEnergy <= 0) {
-                        continue;
-                    }
-                    long[] uidStats = mPowerStats.uidStats.get(uid);
-                    if (uidStats == null) {
-                        uidStats = new long[mLayout.getUidStatsArrayLength()];
-                        mPowerStats.uidStats.put(uid, uidStats);
-                    }
-
-                    mLayout.setUidConsumedEnergy(uidStats, 0,
-                            mLayout.getUidConsumedEnergy(uidStats, 0)
-                                    + uJtoUc(deltaEnergy, averageVoltage));
-                }
-            }
+        if (!mConsumedEnergyHelper.collectConsumedEnergy(mPowerStats, mLayout)) {
+            return null;
         }
+
         long timestamp = mClock.elapsedRealtime();
         mPowerStats.durationMs = timestamp - mLastUpdateTimestamp;
         mLastUpdateTimestamp = timestamp;
-        mFirstCollection = false;
         return mPowerStats;
     }
-
-    @Override
-    protected void onUidRemoved(int uid) {
-        mLastConsumerEnergyPerUid.delete(uid);
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java
index 168a874..c1b4c31 100644
--- a/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java
@@ -19,12 +19,13 @@
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 
+import com.android.server.power.stats.format.GnssPowerStatsLayout;
+
 public class GnssPowerStatsCollector extends EnergyConsumerPowerStatsCollector {
 
-    GnssPowerStatsCollector(Injector injector) {
+    public GnssPowerStatsCollector(Injector injector) {
         super(injector, BatteryConsumer.POWER_COMPONENT_GNSS,
                 BatteryConsumer.powerComponentIdToString(BatteryConsumer.POWER_COMPONENT_GNSS),
-                EnergyConsumerType.GNSS, /* energy consumer name */ null,
-                new GnssPowerStatsLayout());
+                EnergyConsumerType.GNSS, new GnssPowerStatsLayout());
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index c88e1b0..cbd6fab 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -35,12 +35,12 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
 
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
-import java.util.function.IntSupplier;
 import java.util.function.LongSupplier;
 import java.util.function.Supplier;
 
@@ -56,8 +56,6 @@
 
     private static final long MODEM_ACTIVITY_REQUEST_TIMEOUT = 20000;
 
-    private static final long ENERGY_UNSPECIFIED = -1;
-
     @VisibleForTesting
     @AccessNetworkConstants.RadioAccessNetworkType
     static final int[] NETWORK_TYPES = {
@@ -77,14 +75,13 @@
                 long elapsedRealtimeMs, long uptimeMs);
     }
 
-    interface Injector {
+    public interface Injector {
         Handler getHandler();
         Clock getClock();
         PowerStatsUidResolver getUidResolver();
         long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
         PackageManager getPackageManager();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
-        IntSupplier getVoltageSupplier();
         Supplier<NetworkStats> getMobileNetworkStatsSupplier();
         TelephonyManager getTelephonyManager();
         LongSupplier getCallDurationSupplier();
@@ -103,18 +100,14 @@
     private LongSupplier mCallDurationSupplier;
     private LongSupplier mScanDurationSupplier;
     private volatile Supplier<NetworkStats> mNetworkStatsSupplier;
-    private ConsumedEnergyRetriever mConsumedEnergyRetriever;
-    private IntSupplier mVoltageSupplier;
-    private int[] mEnergyConsumerIds = new int[0];
+    private ConsumedEnergyHelper mConsumedEnergyHelper;
     private long mLastUpdateTimestampMillis;
     private ModemActivityInfo mLastModemActivityInfo;
     private NetworkStats mLastNetworkStats;
-    private long[] mLastConsumedEnergyUws;
-    private int mLastVoltageMv;
     private long mLastCallDuration;
     private long mLastScanDuration;
 
-    MobileRadioPowerStatsCollector(Injector injector, Observer observer) {
+    public MobileRadioPowerStatsCollector(Injector injector, Observer observer) {
         super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
                         BatteryConsumer.powerComponentIdToString(
                                 BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)),
@@ -144,34 +137,22 @@
             return false;
         }
 
-        mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
-        mVoltageSupplier = mInjector.getVoltageSupplier();
-
         mTelephonyManager = mInjector.getTelephonyManager();
         mNetworkStatsSupplier = mInjector.getMobileNetworkStatsSupplier();
         mCallDurationSupplier = mInjector.getCallDurationSupplier();
         mScanDurationSupplier = mInjector.getPhoneSignalScanDurationSupplier();
 
-        mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(
+        mConsumedEnergyHelper = new ConsumedEnergyHelper(mInjector.getConsumedEnergyRetriever(),
                 EnergyConsumerType.MOBILE_RADIO);
-        mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
-        Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
 
-        mLayout = new MobileRadioPowerStatsLayout();
-        mLayout.addDeviceMobileActivity();
-        mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length);
-        mLayout.addStateStats();
-        mLayout.addUidNetworkStats();
-        mLayout.addDeviceSectionUsageDuration();
-        mLayout.addDeviceSectionPowerEstimate();
-        mLayout.addUidSectionPowerEstimate();
+        mLayout = new MobileRadioPowerStatsLayout(mConsumedEnergyHelper.getEnergyConsumerCount());
 
         SparseArray<String> stateLabels = new SparseArray<>();
         for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
             final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
                     ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
             for (int freq = 0; freq < freqCount; freq++) {
-                int stateKey = makeStateKey(rat, freq);
+                int stateKey = MobileRadioPowerStatsLayout.makeStateKey(rat, freq);
                 StringBuilder sb = new StringBuilder();
                 if (rat != BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER) {
                     sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]);
@@ -200,7 +181,7 @@
     }
 
     @Override
-    protected PowerStats collectStats() {
+    public PowerStats collectStats() {
         if (!ensureInitialized()) {
             return null;
         }
@@ -210,9 +191,8 @@
 
         ModemActivityInfo modemActivityDelta = collectModemActivityInfo();
         List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas = collectNetworkStats();
-        if (mEnergyConsumerIds.length != 0) {
-            collectEnergyConsumers();
-        }
+
+        mConsumedEnergyHelper.collectConsumedEnergy(mPowerStats, mLayout);
 
         if (mPowerStats.durationMs == 0) {
             setTimestamp(mClock.elapsedRealtime());
@@ -346,65 +326,8 @@
         return delta;
     }
 
-    private void collectEnergyConsumers() {
-        int voltageMv = mVoltageSupplier.getAsInt();
-        if (voltageMv <= 0) {
-            Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
-                    + " mV) when querying energy consumers");
-            return;
-        }
-
-        int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
-        mLastVoltageMv = voltageMv;
-
-        long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
-        if (energyUws == null) {
-            return;
-        }
-
-        for (int i = energyUws.length - 1; i >= 0; i--) {
-            long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
-                    ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
-            if (energyDelta < 0) {
-                // Likely, restart of powerstats HAL
-                energyDelta = 0;
-            }
-            mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
-            mLastConsumedEnergyUws[i] = energyUws[i];
-        }
-    }
-
-    static int makeStateKey(int rat, int freqRange) {
-        if (rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR) {
-            return rat | (freqRange << 8);
-        } else {
-            return rat;
-        }
-    }
-
     private void setTimestamp(long timestamp) {
         mPowerStats.durationMs = Math.max(timestamp - mLastUpdateTimestampMillis, 0);
         mLastUpdateTimestampMillis = timestamp;
     }
-
-    @BatteryStats.RadioAccessTechnology
-    static int mapRadioAccessNetworkTypeToRadioAccessTechnology(
-            @AccessNetworkConstants.RadioAccessNetworkType int networkType) {
-        switch (networkType) {
-            case AccessNetworkConstants.AccessNetworkType.NGRAN:
-                return BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR;
-            case AccessNetworkConstants.AccessNetworkType.EUTRAN:
-                return BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE;
-            case AccessNetworkConstants.AccessNetworkType.UNKNOWN: //fallthrough
-            case AccessNetworkConstants.AccessNetworkType.GERAN: //fallthrough
-            case AccessNetworkConstants.AccessNetworkType.UTRAN: //fallthrough
-            case AccessNetworkConstants.AccessNetworkType.CDMA2000: //fallthrough
-            case AccessNetworkConstants.AccessNetworkType.IWLAN:
-                return BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
-            default:
-                Slog.w(TAG,
-                        "Unhandled RadioAccessNetworkType (" + networkType + "), mapping to OTHER");
-                return BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerAttributor.java b/services/core/java/com/android/server/power/stats/PowerAttributor.java
new file mode 100644
index 0000000..d1f8564
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/PowerAttributor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryUsageStats;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.os.BatteryStatsHistory;
+
+public interface PowerAttributor {
+
+    /**
+     * Returns true if the specified power component can be handled by this PowerAttributor
+     */
+    boolean isPowerComponentSupported(@BatteryConsumer.PowerComponentId int powerComponentId);
+
+    /**
+     * Performs the power attribution calculations and returns the results by populating the
+     * supplied BatteryUsageStats.Builder
+     */
+    void estimatePowerConsumption(BatteryUsageStats.Builder batteryUsageStatsBuilder,
+            BatteryStatsHistory batteryHistory, long monotonicStartTime, long monotonicEndTime);
+
+    /**
+     * Computes estimated power consumption attribution for the specified time range and stores
+     * it in PowerStatsStore for potential accumulation.
+     *
+     * Returns the monotonic timestamp of the last processed history item.
+     */
+    long storeEstimatedPowerConsumption(BatteryStatsHistory batteryStatsHistory, long startTime,
+            long endTimeMs);
+
+    /**
+     * Returns the monotonic timestamp of the last processed history item, stored in
+     * PowerStatsStore.
+     */
+    long getLastSavedEstimatesPowerConsumptionTimestamp();
+
+    /**
+     * Performs the power attribution calculation and prints the results.
+     */
+    void dumpEstimatedPowerConsumption(IndentingPrintWriter ipw,
+            BatteryStatsHistory batteryStatsHistory, long startTime, long endTime);
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index f5b0005..291f0e3 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.ConditionVariable;
@@ -26,13 +27,16 @@
 import android.power.PowerStatsInternal;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
+import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.PowerStatsLayout;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -41,6 +45,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
+import java.util.function.IntSupplier;
 
 /**
  * Collects snapshots of power-related system statistics.
@@ -53,6 +58,8 @@
     private static final String TAG = "PowerStatsCollector";
     private static final int MILLIVOLTS_PER_VOLT = 1000;
     private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
+    private static final long ENERGY_UNSPECIFIED = -1;
+
     private final Handler mHandler;
     protected final PowerStatsUidResolver mUidResolver;
     protected final Clock mClock;
@@ -235,46 +242,31 @@
         return (deltaEnergyUj * MILLIVOLTS_PER_VOLT + (avgVoltageMv / 2)) / avgVoltageMv;
     }
 
-    interface ConsumedEnergyRetriever {
+    public interface ConsumedEnergyRetriever {
+
         @NonNull
-        int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType, String name);
+        int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType);
 
         String getEnergyConsumerName(int energyConsumerId);
 
         @Nullable
         EnergyConsumerResult[] getConsumedEnergy(int[] energyConsumerIds);
 
-        @Nullable
-        default long[] getConsumedEnergyUws(int[] energyConsumerIds) {
-            EnergyConsumerResult[] results = getConsumedEnergy(energyConsumerIds);
-            if (results == null) {
-                return null;
-            }
-
-            long[] energy = new long[energyConsumerIds.length];
-            for (int i = 0; i < energyConsumerIds.length; i++) {
-                int id = energyConsumerIds[i];
-                for (EnergyConsumerResult result : results) {
-                    if (result.id == id) {
-                        energy[i] = result.energyUWs;
-                        break;
-                    }
-                }
-            }
-            return energy;
-        }
-
-        default int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType) {
-            return getEnergyConsumerIds(energyConsumerType, null);
-        }
+        /**
+         * Returns the last known battery/charger voltage in milli-volts.
+         */
+        int getVoltageMv();
     }
 
     static class ConsumedEnergyRetrieverImpl implements ConsumedEnergyRetriever {
         private final PowerStatsInternal mPowerStatsInternal;
+        private final IntSupplier mVoltageSupplier;
         private EnergyConsumer[] mEnergyConsumers;
 
-        ConsumedEnergyRetrieverImpl(PowerStatsInternal powerStatsInternal) {
+        ConsumedEnergyRetrieverImpl(PowerStatsInternal powerStatsInternal,
+                IntSupplier voltageSupplier) {
             mPowerStatsInternal = powerStatsInternal;
+            mVoltageSupplier = voltageSupplier;
         }
 
         private void ensureEnergyConsumers() {
@@ -293,8 +285,9 @@
             }
         }
 
+        @NonNull
         @Override
-        public int[] getEnergyConsumerIds(int energyConsumerType, String name) {
+        public int[] getEnergyConsumerIds(int energyConsumerType) {
             ensureEnergyConsumers();
 
             if (mEnergyConsumers.length == 0) {
@@ -303,8 +296,7 @@
 
             List<EnergyConsumer> energyConsumers = new ArrayList<>();
             for (EnergyConsumer energyConsumer : mEnergyConsumers) {
-                if (energyConsumer.type == energyConsumerType
-                        && (name == null || name.equals(energyConsumer.name))) {
+                if (energyConsumer.type == energyConsumerType) {
                     energyConsumers.add(energyConsumer);
                 }
             }
@@ -335,6 +327,11 @@
         }
 
         @Override
+        public int getVoltageMv() {
+            return mVoltageSupplier.getAsInt();
+        }
+
+        @Override
         public String getEnergyConsumerName(int energyConsumerId) {
             ensureEnergyConsumers();
 
@@ -368,4 +365,152 @@
             return sb.toString();
         }
     }
+
+    class ConsumedEnergyHelper implements PowerStatsUidResolver.Listener {
+        private final ConsumedEnergyRetriever mConsumedEnergyRetriever;
+        private final @EnergyConsumerType int mEnergyConsumerType;
+        private final boolean mPerUidAttributionSupported;
+
+        private boolean mIsInitialized;
+        private boolean mFirstCollection = true;
+        private int[] mEnergyConsumerIds;
+        private long[] mLastConsumedEnergyUws;
+        private final SparseLongArray mLastConsumerEnergyPerUid;
+        private int mLastVoltageMv;
+
+        ConsumedEnergyHelper(ConsumedEnergyRetriever consumedEnergyRetriever,
+                @EnergyConsumerType int energyConsumerType) {
+            mConsumedEnergyRetriever = consumedEnergyRetriever;
+            mEnergyConsumerType = energyConsumerType;
+            mPerUidAttributionSupported = false;
+            mLastConsumerEnergyPerUid = null;
+        }
+
+        ConsumedEnergyHelper(ConsumedEnergyRetriever consumedEnergyRetriever,
+                int energyConsumerId, boolean perUidAttributionSupported) {
+            mConsumedEnergyRetriever = consumedEnergyRetriever;
+            mEnergyConsumerType = EnergyConsumerType.OTHER;
+            mEnergyConsumerIds = new int[]{energyConsumerId};
+            mPerUidAttributionSupported = perUidAttributionSupported;
+            mLastConsumerEnergyPerUid = mPerUidAttributionSupported ? new SparseLongArray() : null;
+        }
+
+        private void ensureInitialized() {
+            if (!mIsInitialized) {
+                if (mEnergyConsumerIds == null) {
+                    mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(
+                            mEnergyConsumerType);
+                }
+                mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
+                Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
+                mUidResolver.addListener(this);
+                mIsInitialized = true;
+            }
+        }
+
+        int getEnergyConsumerCount() {
+            ensureInitialized();
+            return mEnergyConsumerIds.length;
+        }
+
+        boolean collectConsumedEnergy(PowerStats powerStats, PowerStatsLayout layout) {
+            ensureInitialized();
+
+            if (mEnergyConsumerIds.length == 0) {
+                return false;
+            }
+
+            int voltageMv = mConsumedEnergyRetriever.getVoltageMv();
+            int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
+            if (averageVoltage <= 0) {
+                Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
+                        + " mV) when querying energy consumers");
+                return false;
+            }
+
+            mLastVoltageMv = voltageMv;
+
+            EnergyConsumerResult[] energy =
+                    mConsumedEnergyRetriever.getConsumedEnergy(mEnergyConsumerIds);
+            System.out.println("mEnergyConsumerIds = " + Arrays.toString(mEnergyConsumerIds) + " "
+                    + "energy = "
+                    + Arrays.toString(energy));
+            if (energy == null) {
+                return false;
+            }
+
+            for (int i = 0; i < mEnergyConsumerIds.length; i++) {
+                populatePowerStats(powerStats, layout, energy, i, averageVoltage);
+            }
+            mFirstCollection = false;
+            return true;
+        }
+
+        private void populatePowerStats(PowerStats powerStats, PowerStatsLayout layout,
+                @NonNull EnergyConsumerResult[] energy, int energyConsumerIndex,
+                int averageVoltage) {
+            long consumedEnergy = energy[energyConsumerIndex].energyUWs;
+            long energyDelta = mLastConsumedEnergyUws[energyConsumerIndex] != ENERGY_UNSPECIFIED
+                    ? consumedEnergy - mLastConsumedEnergyUws[energyConsumerIndex] : 0;
+            mLastConsumedEnergyUws[energyConsumerIndex] = consumedEnergy;
+            if (energyDelta < 0) {
+                // Likely, restart of powerstats HAL
+                energyDelta = 0;
+            }
+
+            if (energyDelta == 0 && !mFirstCollection) {
+                return;
+            }
+
+            layout.setConsumedEnergy(powerStats.stats, energyConsumerIndex,
+                    uJtoUc(energyDelta, averageVoltage));
+
+            if (!mPerUidAttributionSupported) {
+                return;
+            }
+
+            EnergyConsumerAttribution[] perUid = energy[energyConsumerIndex].attribution;
+            if (perUid == null) {
+                return;
+            }
+
+            for (EnergyConsumerAttribution attribution : perUid) {
+                int uid = mUidResolver.mapUid(attribution.uid);
+                long lastEnergy = mLastConsumerEnergyPerUid.get(uid, ENERGY_UNSPECIFIED);
+                mLastConsumerEnergyPerUid.put(uid, attribution.energyUWs);
+                if (lastEnergy == ENERGY_UNSPECIFIED) {
+                    continue;
+                }
+                long deltaEnergy = attribution.energyUWs - lastEnergy;
+                if (deltaEnergy <= 0) {
+                    continue;
+                }
+
+                long[] uidStats = powerStats.uidStats.get(uid);
+                if (uidStats == null) {
+                    uidStats = new long[layout.getUidStatsArrayLength()];
+                    powerStats.uidStats.put(uid, uidStats);
+                }
+
+                layout.setUidConsumedEnergy(uidStats, energyConsumerIndex,
+                        layout.getUidConsumedEnergy(uidStats, energyConsumerIndex)
+                                + uJtoUc(deltaEnergy, averageVoltage));
+            }
+        }
+
+        @Override
+        public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
+            if (mLastConsumerEnergyPerUid != null) {
+                mHandler.post(() -> mLastConsumerEnergyPerUid.delete(isolatedUid));
+            }
+        }
+
+        @Override
+        public void onIsolatedUidAdded(int isolatedUid, int parentUid) {
+        }
+
+        @Override
+        public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
index abe4c0c..38ca087 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -23,6 +23,7 @@
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.Clock;
 import com.android.internal.os.MonotonicClock;
 
@@ -51,7 +52,8 @@
     private final Handler mHandler;
     private final Runnable mPowerStatsCollector;
     private final Supplier<Long> mEarliestAvailableBatteryHistoryTimeMs;
-    private final PowerStatsAggregator mPowerStatsAggregator;
+    private final BatteryStatsHistory mBatteryStatsHistory;
+    private final PowerAttributor mPowerAttributor;
     private long mLastSavedSpanEndMonotonicTime;
 
     /**
@@ -66,12 +68,13 @@
     }
 
     public PowerStatsScheduler(Runnable powerStatsCollector,
-            PowerStatsAggregator powerStatsAggregator,
+            BatteryStatsHistory batteryStatsHistory, PowerAttributor powerAttributor,
             @DurationMillisLong long aggregatedPowerStatsSpanDuration,
             @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
             AlarmScheduler alarmScheduler, Clock clock, MonotonicClock monotonicClock,
             Supplier<Long> earliestAvailableBatteryHistoryTimeMs, Handler handler) {
-        mPowerStatsAggregator = powerStatsAggregator;
+        mBatteryStatsHistory = batteryStatsHistory;
+        mPowerAttributor = powerAttributor;
         mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
         mPowerStatsAggregationPeriod = powerStatsAggregationPeriod;
         mPowerStatsStore = powerStatsStore;
@@ -123,12 +126,8 @@
         long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
                 mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
         while (endTimeMs <= currentMonotonicTime) {
-            mPowerStatsAggregator.aggregatePowerStats(startTime, endTimeMs,
-                    stats -> {
-                        storeAggregatedPowerStats(stats);
-                        mLastSavedSpanEndMonotonicTime = stats.getStartTime() + stats.getDuration();
-                    });
-
+            mLastSavedSpanEndMonotonicTime = mPowerAttributor.storeEstimatedPowerConsumption(
+                    mBatteryStatsHistory, startTime, endTimeMs);
             startTime = endTimeMs;
             endTimeMs += mAggregatedPowerStatsSpanDuration;
         }
@@ -153,15 +152,8 @@
             mPowerStatsStore.dump(ipw);
             // Aggregate the remainder of power stats and dump the results without storing them yet.
             long powerStoreEndMonotonicTime = getLastSavedSpanEndMonotonicTime();
-            mPowerStatsAggregator.aggregatePowerStats(powerStoreEndMonotonicTime,
-                    MonotonicClock.UNDEFINED,
-                    stats -> {
-                        // Create a PowerStatsSpan for consistency of the textual output
-                        PowerStatsSpan span = PowerStatsStore.createPowerStatsSpan(stats);
-                        if (span != null) {
-                            span.dump(ipw);
-                        }
-                    });
+            mPowerAttributor.dumpEstimatedPowerConsumption(ipw, mBatteryStatsHistory,
+                    powerStoreEndMonotonicTime, MonotonicClock.UNDEFINED);
         });
 
         awaitCompletion();
@@ -223,28 +215,13 @@
     }
 
     private long getLastSavedSpanEndMonotonicTime() {
-        if (mLastSavedSpanEndMonotonicTime != 0) {
-            return mLastSavedSpanEndMonotonicTime;
-        }
-
-        mLastSavedSpanEndMonotonicTime = -1;
-        for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) {
-            if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
-                for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) {
-                    long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration;
-                    if (endMonotonicTime > mLastSavedSpanEndMonotonicTime) {
-                        mLastSavedSpanEndMonotonicTime = endMonotonicTime;
-                    }
-                }
-            }
+        if (mLastSavedSpanEndMonotonicTime == 0) {
+            mLastSavedSpanEndMonotonicTime =
+                    mPowerAttributor.getLastSavedEstimatesPowerConsumptionTimestamp();
         }
         return mLastSavedSpanEndMonotonicTime;
     }
 
-    private void storeAggregatedPowerStats(AggregatedPowerStats stats) {
-        mPowerStatsStore.storeAggregatedPowerStats(stats);
-    }
-
     private void awaitCompletion() {
         ConditionVariable done = new ConditionVariable();
         mHandler.post(done::open);
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
index 4df919d..fc0611f 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java
@@ -44,6 +44,7 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -72,7 +73,7 @@
     private static final DateTimeFormatter DATE_FORMAT =
             DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
 
-    static class TimeFrame {
+    public static class TimeFrame {
         public final long startMonotonicTime;
         @CurrentTimeMillisLong
         public final long startTime;
@@ -119,7 +120,7 @@
         }
     }
 
-    static class Metadata {
+    public static class Metadata {
         static final Comparator<Metadata> COMPARATOR = Comparator.comparing(Metadata::getId);
 
         private final long mId;
@@ -262,7 +263,7 @@
     public abstract static class Section {
         private final String mType;
 
-        Section(String type) {
+        protected Section(String type) {
             mType = type;
         }
 
@@ -274,7 +275,10 @@
             return mType;
         }
 
-        abstract void write(TypedXmlSerializer serializer) throws IOException;
+        /**
+         * Adds the contents of this section to the XML doc.
+         */
+        public abstract void write(TypedXmlSerializer serializer) throws IOException;
 
         /**
          * Prints the section type.
@@ -290,6 +294,11 @@
      */
     public interface SectionReader {
         /**
+         * Returns the unique type of content handled by this reader.
+         */
+        String getType();
+
+        /**
          * Reads the contents of the section using the parser. The type of the object
          * read and the corresponding XML format are determined by the section type.
          */
@@ -316,12 +325,18 @@
         return mMetadata.mId;
     }
 
-    void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime,
+    /**
+     * Adds a time frame covered by this PowerStats span
+     */
+    public void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime,
             @DurationMillisLong long duration) {
         mMetadata.mTimeFrames.add(new TimeFrame(monotonicTime, wallClockTime, duration));
     }
 
-    void addSection(Section section) {
+    /**
+     * Adds the supplied section to the span.
+     */
+    public void addSection(Section section) {
         mMetadata.addSection(section.getType());
         mSections.add(section);
     }
@@ -354,7 +369,7 @@
 
     @Nullable
     static PowerStatsSpan read(InputStream in, TypedXmlPullParser parser,
-            SectionReader sectionReader, String... sectionTypes)
+            Map<String, SectionReader> sectionReaders, String... sectionTypes)
             throws IOException, XmlPullParserException {
         Set<String> neededSections = Sets.newArraySet(sectionTypes);
         boolean selectSections = !neededSections.isEmpty();
@@ -386,7 +401,11 @@
                 if (tag.equals(XML_TAG_SECTION)) {
                     String sectionType = parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE);
                     if (!selectSections || neededSections.contains(sectionType)) {
-                        Section section = sectionReader.read(sectionType, parser);
+                        Section section = null;
+                        SectionReader sectionReader = sectionReaders.get(sectionType);
+                        if (sectionReader != null) {
+                            section = sectionReader.read(sectionType, parser);
+                        }
                         if (section == null) {
                             if (selectSections) {
                                 throw new XmlPullParserException(
@@ -396,11 +415,11 @@
                                     @Override
                                     public void dump(IndentingPrintWriter ipw) {
                                         ipw.println("Unsupported PowerStatsStore section type: "
-                                                    + sectionType);
+                                                + sectionType);
                                     }
 
                                     @Override
-                                    void write(TypedXmlSerializer serializer) {
+                                    public void write(TypedXmlSerializer serializer) {
                                     }
                                 };
                             }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
index 7bcdc71..a875c30 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsStore.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java
@@ -42,6 +42,7 @@
 import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -66,28 +67,31 @@
     private FileLock mJvmLock;
     private final long mMaxStorageBytes;
     private final Handler mHandler;
-    private final PowerStatsSpan.SectionReader mSectionReader;
+    private final Map<String, PowerStatsSpan.SectionReader> mSectionReaders = new HashMap<>();
     private volatile List<PowerStatsSpan.Metadata> mTableOfContents;
 
-    public PowerStatsStore(@NonNull File systemDir, Handler handler,
-            AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
-        this(systemDir, MAX_POWER_STATS_SPAN_STORAGE_BYTES, handler,
-                new DefaultSectionReader(aggregatedPowerStatsConfig));
+    public PowerStatsStore(@NonNull File systemDir, Handler handler) {
+        this(systemDir, MAX_POWER_STATS_SPAN_STORAGE_BYTES, handler);
     }
 
     @VisibleForTesting
-    public PowerStatsStore(@NonNull File systemDir, long maxStorageBytes, Handler handler,
-            @NonNull PowerStatsSpan.SectionReader sectionReader) {
+    public PowerStatsStore(@NonNull File systemDir, long maxStorageBytes, Handler handler) {
         mSystemDir = systemDir;
         mStoreDir = new File(systemDir, POWER_STATS_DIR);
         mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME);
         mHandler = handler;
         mMaxStorageBytes = maxStorageBytes;
-        mSectionReader = sectionReader;
         mHandler.post(this::maybeClearLegacyStore);
     }
 
     /**
+     * Registers a Reader for a section type, which is determined by `sectionReader.getType()`
+     */
+    public void addSectionReader(PowerStatsSpan.SectionReader sectionReader) {
+        mSectionReaders.put(sectionReader.getType(), sectionReader);
+    }
+
+    /**
      * Returns the metadata for all {@link PowerStatsSpan}'s contained in the store.
      */
     @NonNull
@@ -169,7 +173,7 @@
         try {
             File file = makePowerStatsSpanFilename(id);
             try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
-                return PowerStatsSpan.read(inputStream, parser, mSectionReader, sectionTypes);
+                return PowerStatsSpan.read(inputStream, parser, mSectionReaders, sectionTypes);
             } catch (IOException | XmlPullParserException e) {
                 Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file, e);
             }
@@ -179,41 +183,6 @@
         return null;
     }
 
-    void storeAggregatedPowerStats(AggregatedPowerStats stats) {
-        PowerStatsSpan span = createPowerStatsSpan(stats);
-        if (span == null) {
-            return;
-        }
-        storePowerStatsSpan(span);
-    }
-
-    static PowerStatsSpan createPowerStatsSpan(AggregatedPowerStats stats) {
-        List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
-        if (clockUpdates.isEmpty()) {
-            Slog.w(TAG, "No clock updates in aggregated power stats " + stats);
-            return null;
-        }
-
-        long monotonicTime = clockUpdates.get(0).monotonicTime;
-        long durationSum = 0;
-        PowerStatsSpan span = new PowerStatsSpan(monotonicTime);
-        for (int i = 0; i < clockUpdates.size(); i++) {
-            AggregatedPowerStats.ClockUpdate clockUpdate = clockUpdates.get(i);
-            long duration;
-            if (i == clockUpdates.size() - 1) {
-                duration = stats.getDuration() - durationSum;
-            } else {
-                duration = clockUpdate.monotonicTime - monotonicTime;
-            }
-            span.addTimeFrame(clockUpdate.monotonicTime, clockUpdate.currentTime, duration);
-            monotonicTime = clockUpdate.monotonicTime;
-            durationSum += duration;
-        }
-
-        span.addSection(new AggregatedPowerStatsSection(stats));
-        return span;
-    }
-
     /**
      * Stores a {@link PowerStatsSpan} containing a single section for the supplied
      * battery usage stats.
@@ -344,28 +313,4 @@
         }
         ipw.decreaseIndent();
     }
-
-    private static class DefaultSectionReader implements PowerStatsSpan.SectionReader {
-        private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
-
-        DefaultSectionReader(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
-            mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
-        }
-
-        @Override
-        public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
-                throws IOException, XmlPullParserException {
-            switch (sectionType) {
-                case AggregatedPowerStatsSection.TYPE:
-                    return new AggregatedPowerStatsSection(
-                            AggregatedPowerStats.createFromXml(parser,
-                                    mAggregatedPowerStatsConfig));
-                case BatteryUsageStatsSection.TYPE:
-                    return new BatteryUsageStatsSection(
-                            BatteryUsageStats.createFromXml(parser));
-                default:
-                    return null;
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
index 291f289..8371e66 100644
--- a/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
@@ -21,19 +21,16 @@
 import android.os.BatteryStats;
 import android.os.Handler;
 import android.os.PersistableBundle;
-import android.util.Slog;
 import android.util.SparseLongArray;
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
-
-import java.util.Arrays;
-import java.util.function.IntSupplier;
+import com.android.server.power.stats.format.ScreenPowerStatsLayout;
 
 public class ScreenPowerStatsCollector extends PowerStatsCollector {
     private static final String TAG = "ScreenPowerStatsCollector";
 
-    interface ScreenUsageTimeRetriever {
+    public interface ScreenUsageTimeRetriever {
         interface Callback {
             void onUidTopActivityTime(int uid, long topActivityTimeMs);
         }
@@ -45,30 +42,23 @@
         long getScreenDozeTimeMs(int display);
     }
 
-    interface Injector {
+    public interface Injector {
         Handler getHandler();
         Clock getClock();
         PowerStatsUidResolver getUidResolver();
         long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
-        IntSupplier getVoltageSupplier();
         ScreenUsageTimeRetriever getScreenUsageTimeRetriever();
         int getDisplayCount();
     }
 
-    private static final long ENERGY_UNSPECIFIED = -1;
-
     private final Injector mInjector;
     private boolean mIsInitialized;
     private ScreenPowerStatsLayout mLayout;
     private int mDisplayCount;
     private PowerStats mPowerStats;
-    private ConsumedEnergyRetriever mConsumedEnergyRetriever;
-    private IntSupplier mVoltageSupplier;
+    private ConsumedEnergyHelper mConsumedEnergyHelper;
     private ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
-    private int[] mEnergyConsumerIds = new int[0];
-    private long[] mLastConsumedEnergyUws;
-    private int mLastVoltageMv;
     private boolean mFirstSample = true;
     private long[] mLastScreenOnTime;
     private long[][] mLastBrightnessLevelTime;
@@ -76,7 +66,7 @@
     private final SparseLongArray mLastTopActivityTime = new SparseLongArray();
     private long mLastCollectionTime;
 
-    ScreenPowerStatsCollector(Injector injector) {
+    public ScreenPowerStatsCollector(Injector injector) {
         super(injector.getHandler(),
                 injector.getPowerStatsCollectionThrottlePeriod(
                         BatteryConsumer.powerComponentIdToString(
@@ -95,21 +85,12 @@
         }
 
         mDisplayCount = mInjector.getDisplayCount();
-        mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
-        mVoltageSupplier = mInjector.getVoltageSupplier();
         mScreenUsageTimeRetriever = mInjector.getScreenUsageTimeRetriever();
-        mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(
-                EnergyConsumerType.DISPLAY);
-        mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
-        Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
 
-        mLayout = new ScreenPowerStatsLayout();
-        mLayout.addDeviceScreenUsageDurationSection(mInjector.getDisplayCount());
-        mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length);
-        mLayout.addDeviceSectionUsageDuration();
-        mLayout.addDeviceSectionPowerEstimate();
-        mLayout.addUidTopActivitiyDuration();
-        mLayout.addUidSectionPowerEstimate();
+        mConsumedEnergyHelper = new ConsumedEnergyHelper(mInjector.getConsumedEnergyRetriever(),
+                EnergyConsumerType.DISPLAY);
+        mLayout = new ScreenPowerStatsLayout(mConsumedEnergyHelper.getEnergyConsumerCount(),
+                mInjector.getDisplayCount());
 
         PersistableBundle extras = new PersistableBundle();
         mLayout.toExtras(extras);
@@ -129,14 +110,12 @@
     }
 
     @Override
-    protected PowerStats collectStats() {
+    public PowerStats collectStats() {
         if (!ensureInitialized()) {
             return null;
         }
 
-        if (mEnergyConsumerIds.length != 0) {
-            collectEnergyConsumers();
-        }
+        mConsumedEnergyHelper.collectConsumedEnergy(mPowerStats, mLayout);
 
         for (int display = 0; display < mDisplayCount; display++) {
             long screenOnTimeMs = mScreenUsageTimeRetriever.getScreenOnTimeMs(display);
@@ -192,34 +171,6 @@
         return mPowerStats;
     }
 
-    private void collectEnergyConsumers() {
-        int voltageMv = mVoltageSupplier.getAsInt();
-        if (voltageMv <= 0) {
-            Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
-                    + " mV) when querying energy consumers");
-            return;
-        }
-
-        int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
-        mLastVoltageMv = voltageMv;
-
-        long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
-        if (energyUws == null) {
-            return;
-        }
-
-        for (int i = energyUws.length - 1; i >= 0; i--) {
-            long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
-                    ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
-            if (energyDelta < 0) {
-                // Likely, restart of powerstats HAL
-                energyDelta = 0;
-            }
-            mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
-            mLastConsumedEnergyUws[i] = energyUws[i];
-        }
-    }
-
     @Override
     protected void onUidRemoved(int uid) {
         mLastTopActivityTime.delete(uid);
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
index 90981ada..7a84b05 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
@@ -28,12 +28,11 @@
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.WifiPowerStatsLayout;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
-import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
 public class WifiPowerStatsCollector extends PowerStatsCollector {
@@ -41,15 +40,13 @@
 
     private static final long WIFI_ACTIVITY_REQUEST_TIMEOUT = 20000;
 
-    private static final long ENERGY_UNSPECIFIED = -1;
-
     interface Observer {
         void onWifiPowerStatsRetrieved(WifiActivityEnergyInfo info,
                 List<BatteryStatsImpl.NetworkStatsDelta> delta, long elapsedRealtimeMs,
                 long uptimeMs);
     }
 
-    interface WifiStatsRetriever {
+    public interface WifiStatsRetriever {
         interface Callback {
             void onWifiScanTime(int uid, long scanTimeMs, long batchScanTimeMs);
         }
@@ -58,14 +55,13 @@
         long getWifiActiveDuration();
     }
 
-    interface Injector {
+    public interface Injector {
         Handler getHandler();
         Clock getClock();
         PowerStatsUidResolver getUidResolver();
         long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
         PackageManager getPackageManager();
         ConsumedEnergyRetriever getConsumedEnergyRetriever();
-        IntSupplier getVoltageSupplier();
         Supplier<NetworkStats> getWifiNetworkStatsSupplier();
         WifiManager getWifiManager();
         WifiStatsRetriever getWifiStatsRetriever();
@@ -83,13 +79,9 @@
     private volatile WifiManager mWifiManager;
     private volatile Supplier<NetworkStats> mNetworkStatsSupplier;
     private volatile WifiStatsRetriever mWifiStatsRetriever;
-    private ConsumedEnergyRetriever mConsumedEnergyRetriever;
-    private IntSupplier mVoltageSupplier;
-    private int[] mEnergyConsumerIds = new int[0];
+    private ConsumedEnergyHelper mConsumedEnergyHelper;
     private WifiActivityEnergyInfo mLastWifiActivityInfo;
     private NetworkStats mLastNetworkStats;
-    private long[] mLastConsumedEnergyUws;
-    private int mLastVoltageMv;
 
     private static class WifiScanTimes {
         public long basicScanTimeMs;
@@ -99,7 +91,7 @@
     private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>();
     private long mLastWifiActiveDuration;
 
-    WifiPowerStatsCollector(Injector injector, Observer observer) {
+    public WifiPowerStatsCollector(Injector injector, Observer observer) {
         super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
                         BatteryConsumer.powerComponentIdToString(
                                 BatteryConsumer.POWER_COMPONENT_WIFI)),
@@ -128,25 +120,17 @@
             return false;
         }
 
-        mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
-        mVoltageSupplier = mInjector.getVoltageSupplier();
         mWifiManager = mInjector.getWifiManager();
         mNetworkStatsSupplier = mInjector.getWifiNetworkStatsSupplier();
         mWifiStatsRetriever = mInjector.getWifiStatsRetriever();
         mPowerReportingSupported =
                 mWifiManager != null && mWifiManager.isEnhancedPowerReportingSupported();
 
-        mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI);
-        mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
-        Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
+        mConsumedEnergyHelper = new ConsumedEnergyHelper(mInjector.getConsumedEnergyRetriever(),
+                EnergyConsumerType.WIFI);
 
-        mLayout = new WifiPowerStatsLayout();
-        mLayout.addDeviceWifiActivity(mPowerReportingSupported);
-        mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length);
-        mLayout.addUidNetworkStats();
-        mLayout.addDeviceSectionUsageDuration();
-        mLayout.addDeviceSectionPowerEstimate();
-        mLayout.addUidSectionPowerEstimate();
+        mLayout = new WifiPowerStatsLayout(mConsumedEnergyHelper.getEnergyConsumerCount(),
+                mPowerReportingSupported);
 
         PersistableBundle extras = new PersistableBundle();
         mLayout.toExtras(extras);
@@ -162,7 +146,7 @@
     }
 
     @Override
-    protected PowerStats collectStats() {
+    public PowerStats collectStats() {
         if (!ensureInitialized()) {
             return null;
         }
@@ -176,9 +160,7 @@
         List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas = collectNetworkStats();
         collectWifiScanTime();
 
-        if (mEnergyConsumerIds.length != 0) {
-            collectEnergyConsumers();
-        }
+        mConsumedEnergyHelper.collectConsumedEnergy(mPowerStats, mLayout);
 
         if (mObserver != null) {
             mObserver.onWifiPowerStatsRetrieved(activityInfo, networkStatsDeltas,
@@ -318,34 +300,6 @@
         mLayout.setDeviceBatchedScanTime(mDeviceStats, mScanTimes.batchedScanTimeMs);
     }
 
-    private void collectEnergyConsumers() {
-        int voltageMv = mVoltageSupplier.getAsInt();
-        if (voltageMv <= 0) {
-            Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
-                    + " mV) when querying energy consumers");
-            return;
-        }
-
-        int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
-        mLastVoltageMv = voltageMv;
-
-        long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
-        if (energyUws == null) {
-            return;
-        }
-
-        for (int i = energyUws.length - 1; i >= 0; i--) {
-            long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
-                    ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
-            if (energyDelta < 0) {
-                // Likely, restart of powerstats HAL
-                energyDelta = 0;
-            }
-            mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
-            mLastConsumedEnergyUws[i] = energyUws[i];
-        }
-    }
-
     @Override
     protected void onUidRemoved(int uid) {
         super.onUidRemoved(uid);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/services/core/java/com/android/server/power/stats/format/AmbientDisplayPowerStatsLayout.java
similarity index 73%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
copy to services/core/java/com/android/server/power/stats/format/AmbientDisplayPowerStatsLayout.java
index 3c5beeb..1b99b0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/services/core/java/com/android/server/power/stats/format/AmbientDisplayPowerStatsLayout.java
@@ -13,7 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.server.power.stats.format;
 
-package com.android.wm.shell.common.bubbles;
-
-parcelable BubbleBarLocation;
\ No newline at end of file
+public class AmbientDisplayPowerStatsLayout extends PowerStatsLayout {
+    public AmbientDisplayPowerStatsLayout() {
+        addDeviceSectionPowerEstimate();
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java
similarity index 68%
rename from services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java
index 502337c..4a26d83 100644
--- a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/BinaryStatePowerStatsLayout.java
@@ -14,11 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
-class BinaryStatePowerStatsLayout extends EnergyConsumerPowerStatsLayout {
-    BinaryStatePowerStatsLayout() {
+import com.android.internal.os.PowerStats;
+
+public class BinaryStatePowerStatsLayout extends EnergyConsumerPowerStatsLayout {
+    public BinaryStatePowerStatsLayout() {
         addDeviceSectionUsageDuration();
         addUidSectionUsageDuration();
     }
+
+    public BinaryStatePowerStatsLayout(PowerStats.Descriptor descriptor) {
+        super(descriptor);
+    }
 }
diff --git a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/BluetoothPowerStatsLayout.java
similarity index 88%
rename from services/core/java/com/android/server/power/stats/BluetoothPowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/BluetoothPowerStatsLayout.java
index 9358b5e..534a9f7 100644
--- a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/BluetoothPowerStatsLayout.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
 import android.annotation.NonNull;
 import android.os.PersistableBundle;
@@ -37,21 +37,49 @@
     private int mUidTxBytesPosition;
     private int mUidScanTimePosition;
 
-    BluetoothPowerStatsLayout() {
+    public BluetoothPowerStatsLayout(int energyConsumerCount) {
+        addDeviceBluetoothControllerActivity();
+        addDeviceSectionEnergyConsumers(energyConsumerCount);
+        addDeviceSectionUsageDuration();
+        addDeviceSectionPowerEstimate();
+        addUidTrafficStats();
+        addUidSectionPowerEstimate();
     }
 
-    BluetoothPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+    public BluetoothPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
         super(descriptor);
+        PersistableBundle extras = descriptor.extras;
+        mDeviceRxTimePosition = extras.getInt(EXTRA_DEVICE_RX_TIME_POSITION);
+        mDeviceTxTimePosition = extras.getInt(EXTRA_DEVICE_TX_TIME_POSITION);
+        mDeviceIdleTimePosition = extras.getInt(EXTRA_DEVICE_IDLE_TIME_POSITION);
+        mDeviceScanTimePosition = extras.getInt(EXTRA_DEVICE_SCAN_TIME_POSITION);
+        mUidRxBytesPosition = extras.getInt(EXTRA_UID_RX_BYTES_POSITION);
+        mUidTxBytesPosition = extras.getInt(EXTRA_UID_TX_BYTES_POSITION);
+        mUidScanTimePosition = extras.getInt(EXTRA_UID_SCAN_TIME_POSITION);
     }
 
-    void addDeviceBluetoothControllerActivity() {
+    /**
+     * Copies the elements of the stats array layout into <code>extras</code>
+     */
+    public void toExtras(PersistableBundle extras) {
+        super.toExtras(extras);
+        extras.putInt(EXTRA_DEVICE_RX_TIME_POSITION, mDeviceRxTimePosition);
+        extras.putInt(EXTRA_DEVICE_TX_TIME_POSITION, mDeviceTxTimePosition);
+        extras.putInt(EXTRA_DEVICE_IDLE_TIME_POSITION, mDeviceIdleTimePosition);
+        extras.putInt(EXTRA_DEVICE_SCAN_TIME_POSITION, mDeviceScanTimePosition);
+        extras.putInt(EXTRA_UID_RX_BYTES_POSITION, mUidRxBytesPosition);
+        extras.putInt(EXTRA_UID_TX_BYTES_POSITION, mUidTxBytesPosition);
+        extras.putInt(EXTRA_UID_SCAN_TIME_POSITION, mUidScanTimePosition);
+    }
+
+    private void addDeviceBluetoothControllerActivity() {
         mDeviceRxTimePosition = addDeviceSection(1, "rx");
         mDeviceTxTimePosition = addDeviceSection(1, "tx");
         mDeviceIdleTimePosition = addDeviceSection(1, "idle");
         mDeviceScanTimePosition = addDeviceSection(1, "scan", FLAG_OPTIONAL);
     }
 
-    void addUidTrafficStats() {
+    private void addUidTrafficStats() {
         mUidRxBytesPosition = addUidSection(1, "rx-B");
         mUidTxBytesPosition = addUidSection(1, "tx-B");
         mUidScanTimePosition = addUidSection(1, "scan", FLAG_OPTIONAL);
@@ -112,32 +140,4 @@
     public long getUidScanTime(long[] stats) {
         return stats[mUidScanTimePosition];
     }
-
-    /**
-     * Copies the elements of the stats array layout into <code>extras</code>
-     */
-    public void toExtras(PersistableBundle extras) {
-        super.toExtras(extras);
-        extras.putInt(EXTRA_DEVICE_RX_TIME_POSITION, mDeviceRxTimePosition);
-        extras.putInt(EXTRA_DEVICE_TX_TIME_POSITION, mDeviceTxTimePosition);
-        extras.putInt(EXTRA_DEVICE_IDLE_TIME_POSITION, mDeviceIdleTimePosition);
-        extras.putInt(EXTRA_DEVICE_SCAN_TIME_POSITION, mDeviceScanTimePosition);
-        extras.putInt(EXTRA_UID_RX_BYTES_POSITION, mUidRxBytesPosition);
-        extras.putInt(EXTRA_UID_TX_BYTES_POSITION, mUidTxBytesPosition);
-        extras.putInt(EXTRA_UID_SCAN_TIME_POSITION, mUidScanTimePosition);
-    }
-
-    /**
-     * Retrieves elements of the stats array layout from <code>extras</code>
-     */
-    public void fromExtras(PersistableBundle extras) {
-        super.fromExtras(extras);
-        mDeviceRxTimePosition = extras.getInt(EXTRA_DEVICE_RX_TIME_POSITION);
-        mDeviceTxTimePosition = extras.getInt(EXTRA_DEVICE_TX_TIME_POSITION);
-        mDeviceIdleTimePosition = extras.getInt(EXTRA_DEVICE_IDLE_TIME_POSITION);
-        mDeviceScanTimePosition = extras.getInt(EXTRA_DEVICE_SCAN_TIME_POSITION);
-        mUidRxBytesPosition = extras.getInt(EXTRA_UID_RX_BYTES_POSITION);
-        mUidTxBytesPosition = extras.getInt(EXTRA_UID_TX_BYTES_POSITION);
-        mUidScanTimePosition = extras.getInt(EXTRA_UID_SCAN_TIME_POSITION);
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/CpuPowerStatsLayout.java
similarity index 85%
rename from services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/CpuPowerStatsLayout.java
index 2a02bd0..3186d7d 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/CpuPowerStatsLayout.java
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
+import android.annotation.NonNull;
 import android.os.PersistableBundle;
 
+import com.android.internal.os.PowerStats;
+
 /**
  * Captures the positions and lengths of sections of the stats array, such as time-in-state,
  * power usage estimates etc.
@@ -40,10 +43,59 @@
 
     private int[] mScalingStepToPowerBracketMap;
 
+    public CpuPowerStatsLayout(int energyConsumerCount, int cpuScalingPolicyCount,
+            int[] scalingStepToPowerBracketMap) {
+        addDeviceSectionCpuTimeByScalingStep(scalingStepToPowerBracketMap.length);
+        addDeviceSectionCpuTimeByCluster(cpuScalingPolicyCount);
+        addDeviceSectionUsageDuration();
+        addDeviceSectionEnergyConsumers(energyConsumerCount);
+        addDeviceSectionPowerEstimate();
+        addUidSectionCpuTimeByPowerBracket(scalingStepToPowerBracketMap);
+        addUidSectionPowerEstimate();
+    }
+
+    public CpuPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+        super(descriptor);
+        PersistableBundle extras = descriptor.extras;
+        mDeviceCpuTimeByScalingStepPosition =
+                extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION);
+        mDeviceCpuTimeByScalingStepCount =
+                extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT);
+        mDeviceCpuTimeByClusterPosition =
+                extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION);
+        mDeviceCpuTimeByClusterCount =
+                extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT);
+        mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION);
+        mScalingStepToPowerBracketMap =
+                getIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET);
+        if (mScalingStepToPowerBracketMap == null) {
+            mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount];
+        }
+        updatePowerBracketCount();
+    }
+
+    /**
+     * Copies the elements of the stats array layout into <code>extras</code>
+     */
+    public void toExtras(PersistableBundle extras) {
+        super.toExtras(extras);
+        extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION,
+                mDeviceCpuTimeByScalingStepPosition);
+        extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT,
+                mDeviceCpuTimeByScalingStepCount);
+        extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION,
+                mDeviceCpuTimeByClusterPosition);
+        extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT,
+                mDeviceCpuTimeByClusterCount);
+        extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition);
+        putIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET,
+                mScalingStepToPowerBracketMap);
+    }
+
     /**
      * Declare that the stats array has a section capturing CPU time per scaling step
      */
-    public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
+    private void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
         mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount, "steps");
         mDeviceCpuTimeByScalingStepCount = scalingStepCount;
     }
@@ -71,7 +123,7 @@
     /**
      * Declare that the stats array has a section capturing CPU time in each cluster
      */
-    public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
+    private void addDeviceSectionCpuTimeByCluster(int clusterCount) {
         mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount, "clusters");
         mDeviceCpuTimeByClusterCount = clusterCount;
     }
@@ -99,7 +151,7 @@
     /**
      * Declare that the UID stats array has a section capturing CPU time per power bracket.
      */
-    public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
+    private void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
         mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
         updatePowerBracketCount();
         mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount, "time");
@@ -135,44 +187,4 @@
     public long getUidTimeByPowerBracket(long[] stats, int bracket) {
         return stats[mUidPowerBracketsPosition + bracket];
     }
-
-    /**
-     * Copies the elements of the stats array layout into <code>extras</code>
-     */
-    public void toExtras(PersistableBundle extras) {
-        super.toExtras(extras);
-        extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION,
-                mDeviceCpuTimeByScalingStepPosition);
-        extras.putInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT,
-                mDeviceCpuTimeByScalingStepCount);
-        extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION,
-                mDeviceCpuTimeByClusterPosition);
-        extras.putInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT,
-                mDeviceCpuTimeByClusterCount);
-        extras.putInt(EXTRA_UID_BRACKETS_POSITION, mUidPowerBracketsPosition);
-        putIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET,
-                mScalingStepToPowerBracketMap);
-    }
-
-    /**
-     * Retrieves elements of the stats array layout from <code>extras</code>
-     */
-    public void fromExtras(PersistableBundle extras) {
-        super.fromExtras(extras);
-        mDeviceCpuTimeByScalingStepPosition =
-                extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_POSITION);
-        mDeviceCpuTimeByScalingStepCount =
-                extras.getInt(EXTRA_DEVICE_TIME_BY_SCALING_STEP_COUNT);
-        mDeviceCpuTimeByClusterPosition =
-                extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_POSITION);
-        mDeviceCpuTimeByClusterCount =
-                extras.getInt(EXTRA_DEVICE_TIME_BY_CLUSTER_COUNT);
-        mUidPowerBracketsPosition = extras.getInt(EXTRA_UID_BRACKETS_POSITION);
-        mScalingStepToPowerBracketMap =
-                getIntArray(extras, EXTRA_UID_STATS_SCALING_STEP_TO_POWER_BRACKET);
-        if (mScalingStepToPowerBracketMap == null) {
-            mScalingStepToPowerBracketMap = new int[mDeviceCpuTimeByScalingStepCount];
-        }
-        updatePowerBracketCount();
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/EnergyConsumerPowerStatsLayout.java
similarity index 80%
rename from services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/EnergyConsumerPowerStatsLayout.java
index 8430f56..e7a4822 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/EnergyConsumerPowerStatsLayout.java
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
-class EnergyConsumerPowerStatsLayout extends PowerStatsLayout {
-    EnergyConsumerPowerStatsLayout() {
+import com.android.internal.os.PowerStats;
+
+public class EnergyConsumerPowerStatsLayout extends PowerStatsLayout {
+    public EnergyConsumerPowerStatsLayout() {
         // Add a section for consumed energy, even if the specific device does not
         // have support EnergyConsumers.  This is done to guarantee format compatibility between
         // PowerStats created by a PowerStatsCollector and those produced by a PowerStatsProcessor.
@@ -30,4 +32,8 @@
         addUidSectionEnergyConsumers(1);
         addUidSectionPowerEstimate();
     }
+
+    public EnergyConsumerPowerStatsLayout(PowerStats.Descriptor descriptor) {
+        super(descriptor);
+    }
 }
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/GnssPowerStatsLayout.java
similarity index 81%
rename from services/core/java/com/android/server/power/stats/GnssPowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/GnssPowerStatsLayout.java
index 9a1317d..b70b173 100644
--- a/services/core/java/com/android/server/power/stats/GnssPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/GnssPowerStatsLayout.java
@@ -14,28 +14,31 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
+import android.annotation.NonNull;
 import android.location.GnssSignalQuality;
 import android.os.PersistableBundle;
 
-class GnssPowerStatsLayout extends BinaryStatePowerStatsLayout {
+import com.android.internal.os.PowerStats;
+
+public class GnssPowerStatsLayout extends BinaryStatePowerStatsLayout {
     private static final String EXTRA_DEVICE_TIME_SIGNAL_LEVEL_POSITION = "dt-sig";
     private static final String EXTRA_UID_TIME_SIGNAL_LEVEL_POSITION = "ut-sig";
 
-    private int mDeviceSignalLevelTimePosition;
-    private int mUidSignalLevelTimePosition;
+    private final int mDeviceSignalLevelTimePosition;
+    private final int mUidSignalLevelTimePosition;
 
-    GnssPowerStatsLayout() {
+    public GnssPowerStatsLayout() {
         mDeviceSignalLevelTimePosition = addDeviceSection(
                 GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS, "level");
         mUidSignalLevelTimePosition = addUidSection(
                 GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS, "level");
     }
 
-    @Override
-    public void fromExtras(PersistableBundle extras) {
-        super.fromExtras(extras);
+    public GnssPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+        super(descriptor);
+        PersistableBundle extras = descriptor.extras;
         mDeviceSignalLevelTimePosition = extras.getInt(EXTRA_DEVICE_TIME_SIGNAL_LEVEL_POSITION);
         mUidSignalLevelTimePosition = extras.getInt(EXTRA_UID_TIME_SIGNAL_LEVEL_POSITION);
     }
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/MobileRadioPowerStatsLayout.java
similarity index 79%
rename from services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/MobileRadioPowerStatsLayout.java
index 07d78f8..da6fc41 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/MobileRadioPowerStatsLayout.java
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
 import android.annotation.NonNull;
+import android.os.BatteryStats;
 import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.ModemActivityInfo;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -28,7 +30,7 @@
  * Captures the positions and lengths of sections of the stats array, such as time-in-state,
  * power usage estimates etc.
  */
-class MobileRadioPowerStatsLayout extends PowerStatsLayout {
+public class MobileRadioPowerStatsLayout extends PowerStatsLayout {
     private static final String TAG = "MobileRadioPowerStatsLayout";
     private static final String EXTRA_DEVICE_SLEEP_TIME_POSITION = "dt-sleep";
     private static final String EXTRA_DEVICE_IDLE_TIME_POSITION = "dt-idle";
@@ -56,27 +58,95 @@
     private int mUidRxPacketsPosition;
     private int mUidTxPacketsPosition;
 
-    MobileRadioPowerStatsLayout() {
+    public MobileRadioPowerStatsLayout(int energyConsumerCount) {
+        addDeviceMobileActivity();
+        addDeviceSectionEnergyConsumers(energyConsumerCount);
+        addStateStats();
+        addUidNetworkStats();
+        addDeviceSectionUsageDuration();
+        addDeviceSectionPowerEstimate();
+        addUidSectionPowerEstimate();
     }
 
-    MobileRadioPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+    public MobileRadioPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
         super(descriptor);
+        PersistableBundle extras = descriptor.extras;
+        mDeviceSleepTimePosition = extras.getInt(EXTRA_DEVICE_SLEEP_TIME_POSITION);
+        mDeviceIdleTimePosition = extras.getInt(EXTRA_DEVICE_IDLE_TIME_POSITION);
+        mDeviceScanTimePosition = extras.getInt(EXTRA_DEVICE_SCAN_TIME_POSITION);
+        mDeviceCallTimePosition = extras.getInt(EXTRA_DEVICE_CALL_TIME_POSITION);
+        mDeviceCallPowerPosition = extras.getInt(EXTRA_DEVICE_CALL_POWER_POSITION);
+        mStateRxTimePosition = extras.getInt(EXTRA_STATE_RX_TIME_POSITION);
+        mStateTxTimesPosition = extras.getInt(EXTRA_STATE_TX_TIMES_POSITION);
+        mStateTxTimesCount = extras.getInt(EXTRA_STATE_TX_TIMES_COUNT);
+        mUidRxBytesPosition = extras.getInt(EXTRA_UID_RX_BYTES_POSITION);
+        mUidTxBytesPosition = extras.getInt(EXTRA_UID_TX_BYTES_POSITION);
+        mUidRxPacketsPosition = extras.getInt(EXTRA_UID_RX_PACKETS_POSITION);
+        mUidTxPacketsPosition = extras.getInt(EXTRA_UID_TX_PACKETS_POSITION);
     }
 
-    void addDeviceMobileActivity() {
+    /**
+     * Copies the elements of the stats array layout into <code>extras</code>
+     */
+    public void toExtras(PersistableBundle extras) {
+        super.toExtras(extras);
+        extras.putInt(EXTRA_DEVICE_SLEEP_TIME_POSITION, mDeviceSleepTimePosition);
+        extras.putInt(EXTRA_DEVICE_IDLE_TIME_POSITION, mDeviceIdleTimePosition);
+        extras.putInt(EXTRA_DEVICE_SCAN_TIME_POSITION, mDeviceScanTimePosition);
+        extras.putInt(EXTRA_DEVICE_CALL_TIME_POSITION, mDeviceCallTimePosition);
+        extras.putInt(EXTRA_DEVICE_CALL_POWER_POSITION, mDeviceCallPowerPosition);
+        extras.putInt(EXTRA_STATE_RX_TIME_POSITION, mStateRxTimePosition);
+        extras.putInt(EXTRA_STATE_TX_TIMES_POSITION, mStateTxTimesPosition);
+        extras.putInt(EXTRA_STATE_TX_TIMES_COUNT, mStateTxTimesCount);
+        extras.putInt(EXTRA_UID_RX_BYTES_POSITION, mUidRxBytesPosition);
+        extras.putInt(EXTRA_UID_TX_BYTES_POSITION, mUidTxBytesPosition);
+        extras.putInt(EXTRA_UID_RX_PACKETS_POSITION, mUidRxPacketsPosition);
+        extras.putInt(EXTRA_UID_TX_PACKETS_POSITION, mUidTxPacketsPosition);
+    }
+
+    public static int makeStateKey(int rat, int freqRange) {
+        if (rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR) {
+            return rat | (freqRange << 8);
+        } else {
+            return rat;
+        }
+    }
+
+    @BatteryStats.RadioAccessTechnology
+    public static int mapRadioAccessNetworkTypeToRadioAccessTechnology(
+            @AccessNetworkConstants.RadioAccessNetworkType int networkType) {
+        switch (networkType) {
+            case AccessNetworkConstants.AccessNetworkType.NGRAN:
+                return BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR;
+            case AccessNetworkConstants.AccessNetworkType.EUTRAN:
+                return BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE;
+            case AccessNetworkConstants.AccessNetworkType.UNKNOWN: //fallthrough
+            case AccessNetworkConstants.AccessNetworkType.GERAN: //fallthrough
+            case AccessNetworkConstants.AccessNetworkType.UTRAN: //fallthrough
+            case AccessNetworkConstants.AccessNetworkType.CDMA2000: //fallthrough
+            case AccessNetworkConstants.AccessNetworkType.IWLAN:
+                return BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+            default:
+                Slog.w(TAG,
+                        "Unhandled RadioAccessNetworkType (" + networkType + "), mapping to OTHER");
+                return BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER;
+        }
+    }
+
+    private void addDeviceMobileActivity() {
         mDeviceSleepTimePosition = addDeviceSection(1, "sleep");
         mDeviceIdleTimePosition = addDeviceSection(1, "idle");
         mDeviceScanTimePosition = addDeviceSection(1, "scan");
         mDeviceCallTimePosition = addDeviceSection(1, "call", FLAG_OPTIONAL);
     }
 
-    void addStateStats() {
+    private void addStateStats() {
         mStateRxTimePosition = addStateSection(1, "rx");
         mStateTxTimesCount = ModemActivityInfo.getNumTxPowerLevels();
         mStateTxTimesPosition = addStateSection(mStateTxTimesCount, "tx");
     }
 
-    void addUidNetworkStats() {
+    private void addUidNetworkStats() {
         mUidRxPacketsPosition = addUidSection(1, "rx-pkts");
         mUidRxBytesPosition = addUidSection(1, "rx-B");
         mUidTxPacketsPosition = addUidSection(1, "tx-pkts");
@@ -84,7 +154,7 @@
     }
 
     @Override
-    public void addDeviceSectionPowerEstimate() {
+    protected void addDeviceSectionPowerEstimate() {
         super.addDeviceSectionPowerEstimate();
         // Printed as part of the PhoneCallPowerStatsProcessor
         mDeviceCallPowerPosition = addDeviceSection(1, "call-power", FLAG_HIDDEN);
@@ -178,44 +248,6 @@
         return stats[mUidTxPacketsPosition];
     }
 
-    /**
-     * Copies the elements of the stats array layout into <code>extras</code>
-     */
-    public void toExtras(PersistableBundle extras) {
-        super.toExtras(extras);
-        extras.putInt(EXTRA_DEVICE_SLEEP_TIME_POSITION, mDeviceSleepTimePosition);
-        extras.putInt(EXTRA_DEVICE_IDLE_TIME_POSITION, mDeviceIdleTimePosition);
-        extras.putInt(EXTRA_DEVICE_SCAN_TIME_POSITION, mDeviceScanTimePosition);
-        extras.putInt(EXTRA_DEVICE_CALL_TIME_POSITION, mDeviceCallTimePosition);
-        extras.putInt(EXTRA_DEVICE_CALL_POWER_POSITION, mDeviceCallPowerPosition);
-        extras.putInt(EXTRA_STATE_RX_TIME_POSITION, mStateRxTimePosition);
-        extras.putInt(EXTRA_STATE_TX_TIMES_POSITION, mStateTxTimesPosition);
-        extras.putInt(EXTRA_STATE_TX_TIMES_COUNT, mStateTxTimesCount);
-        extras.putInt(EXTRA_UID_RX_BYTES_POSITION, mUidRxBytesPosition);
-        extras.putInt(EXTRA_UID_TX_BYTES_POSITION, mUidTxBytesPosition);
-        extras.putInt(EXTRA_UID_RX_PACKETS_POSITION, mUidRxPacketsPosition);
-        extras.putInt(EXTRA_UID_TX_PACKETS_POSITION, mUidTxPacketsPosition);
-    }
-
-    /**
-     * Retrieves elements of the stats array layout from <code>extras</code>
-     */
-    public void fromExtras(PersistableBundle extras) {
-        super.fromExtras(extras);
-        mDeviceSleepTimePosition = extras.getInt(EXTRA_DEVICE_SLEEP_TIME_POSITION);
-        mDeviceIdleTimePosition = extras.getInt(EXTRA_DEVICE_IDLE_TIME_POSITION);
-        mDeviceScanTimePosition = extras.getInt(EXTRA_DEVICE_SCAN_TIME_POSITION);
-        mDeviceCallTimePosition = extras.getInt(EXTRA_DEVICE_CALL_TIME_POSITION);
-        mDeviceCallPowerPosition = extras.getInt(EXTRA_DEVICE_CALL_POWER_POSITION);
-        mStateRxTimePosition = extras.getInt(EXTRA_STATE_RX_TIME_POSITION);
-        mStateTxTimesPosition = extras.getInt(EXTRA_STATE_TX_TIMES_POSITION);
-        mStateTxTimesCount = extras.getInt(EXTRA_STATE_TX_TIMES_COUNT);
-        mUidRxBytesPosition = extras.getInt(EXTRA_UID_RX_BYTES_POSITION);
-        mUidTxBytesPosition = extras.getInt(EXTRA_UID_TX_BYTES_POSITION);
-        mUidRxPacketsPosition = extras.getInt(EXTRA_UID_RX_PACKETS_POSITION);
-        mUidTxPacketsPosition = extras.getInt(EXTRA_UID_TX_PACKETS_POSITION);
-    }
-
     public void addRxTxTimesForRat(SparseArray<long[]> stateStats, int networkType, int freqRange,
             long rxTime, int[] txTime) {
         if (txTime.length != mStateTxTimesCount) {
@@ -239,9 +271,8 @@
             return;
         }
 
-        int rat = MobileRadioPowerStatsCollector.mapRadioAccessNetworkTypeToRadioAccessTechnology(
-                networkType);
-        int stateKey = MobileRadioPowerStatsCollector.makeStateKey(rat, freqRange);
+        int rat = mapRadioAccessNetworkTypeToRadioAccessTechnology(networkType);
+        int stateKey = makeStateKey(rat, freqRange);
         long[] stats = stateStats.get(stateKey);
         if (stats == null) {
             stats = new long[getStateStatsArrayLength()];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/services/core/java/com/android/server/power/stats/format/PhoneCallPowerStatsLayout.java
similarity index 74%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
copy to services/core/java/com/android/server/power/stats/format/PhoneCallPowerStatsLayout.java
index 3c5beeb..5a34148 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
+++ b/services/core/java/com/android/server/power/stats/format/PhoneCallPowerStatsLayout.java
@@ -13,7 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.server.power.stats.format;
 
-package com.android.wm.shell.common.bubbles;
-
-parcelable BubbleBarLocation;
\ No newline at end of file
+public class PhoneCallPowerStatsLayout extends PowerStatsLayout {
+    public PhoneCallPowerStatsLayout() {
+        addDeviceSectionPowerEstimate();
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/PowerStatsLayout.java
similarity index 91%
rename from services/core/java/com/android/server/power/stats/PowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/PowerStatsLayout.java
index 62abfc6..d070919 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/PowerStatsLayout.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
 import android.os.PersistableBundle;
 import android.util.Slog;
@@ -36,7 +36,7 @@
     private static final String EXTRA_UID_ENERGY_CONSUMERS_COUNT = "uec";
     private static final String EXTRA_UID_POWER_POSITION = "up";
 
-    protected static final int UNSUPPORTED = -1;
+    public static final int UNSUPPORTED = -1;
     protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
     protected static final int FLAG_OPTIONAL = 1;
     protected static final int FLAG_HIDDEN = 2;
@@ -46,9 +46,9 @@
     private int mStateStatsArrayLength;
     private int mUidStatsArrayLength;
 
-    private StringBuilder mDeviceFormat = new StringBuilder();
-    private StringBuilder mStateFormat = new StringBuilder();
-    private StringBuilder mUidFormat = new StringBuilder();
+    private final StringBuilder mDeviceFormat = new StringBuilder();
+    private final StringBuilder mStateFormat = new StringBuilder();
+    private final StringBuilder mUidFormat = new StringBuilder();
 
     protected int mDeviceDurationPosition = UNSUPPORTED;
     private int mDeviceEnergyConsumerPosition;
@@ -63,7 +63,32 @@
     }
 
     public PowerStatsLayout(PowerStats.Descriptor descriptor) {
-        fromExtras(descriptor.extras);
+        PersistableBundle extras = descriptor.extras;
+        mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION);
+        mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
+        mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
+        mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
+        mUidDurationPosition = extras.getInt(EXTRA_UID_DURATION_POSITION);
+        mUidEnergyConsumerPosition = extras.getInt(EXTRA_UID_ENERGY_CONSUMERS_POSITION);
+        mUidEnergyConsumerCount = extras.getInt(EXTRA_UID_ENERGY_CONSUMERS_COUNT);
+        mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
+    }
+
+    /**
+     * Copies the elements of the stats array layout into <code>extras</code>
+     */
+    public void toExtras(PersistableBundle extras) {
+        extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition);
+        extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION, mDeviceEnergyConsumerPosition);
+        extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT, mDeviceEnergyConsumerCount);
+        extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
+        extras.putInt(EXTRA_UID_DURATION_POSITION, mUidDurationPosition);
+        extras.putInt(EXTRA_UID_ENERGY_CONSUMERS_POSITION, mUidEnergyConsumerPosition);
+        extras.putInt(EXTRA_UID_ENERGY_CONSUMERS_COUNT, mUidEnergyConsumerCount);
+        extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
+        extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString());
+        extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString());
+        extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, mUidFormat.toString());
     }
 
     public int getDeviceStatsArrayLength() {
@@ -141,7 +166,7 @@
     /**
      * Declare that the stats array has a section capturing usage duration
      */
-    public void addDeviceSectionUsageDuration() {
+    protected void addDeviceSectionUsageDuration() {
         mDeviceDurationPosition = addDeviceSection(1, "usage", FLAG_OPTIONAL);
     }
 
@@ -163,7 +188,7 @@
      * Declares that the stats array has a section capturing EnergyConsumer data from
      * PowerStatsService.
      */
-    public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
+    protected void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
         mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy",
                 FLAG_OPTIONAL);
         mDeviceEnergyConsumerCount = energyConsumerCount;
@@ -192,7 +217,7 @@
     /**
      * Declare that the stats array has a section capturing a power estimate
      */
-    public void addDeviceSectionPowerEstimate() {
+    protected void addDeviceSectionPowerEstimate() {
         mDevicePowerEstimatePosition = addDeviceSection(1, "power",
                 FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
     }
@@ -215,14 +240,14 @@
     /**
      * Declare that the UID stats array has a section capturing usage duration
      */
-    public void addUidSectionUsageDuration() {
+    protected void addUidSectionUsageDuration() {
         mUidDurationPosition = addUidSection(1, "time");
     }
 
     /**
      * Declare that the UID stats array has a section capturing a power estimate
      */
-    public void addUidSectionPowerEstimate() {
+    protected void addUidSectionPowerEstimate() {
         mUidPowerEstimatePosition = addUidSection(1, "power", FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
     }
 
@@ -251,7 +276,7 @@
      * Declares that the UID stats array has a section capturing EnergyConsumer data from
      * PowerStatsService.
      */
-    public void addUidSectionEnergyConsumers(int energyConsumerCount) {
+    protected void addUidSectionEnergyConsumers(int energyConsumerCount) {
         mUidEnergyConsumerPosition = addUidSection(energyConsumerCount, "energy",
                 FLAG_OPTIONAL);
         mUidEnergyConsumerCount = energyConsumerCount;
@@ -292,39 +317,6 @@
         return stats[mUidPowerEstimatePosition] / MILLI_TO_NANO_MULTIPLIER;
     }
 
-    /**
-     * Copies the elements of the stats array layout into <code>extras</code>
-     */
-    public void toExtras(PersistableBundle extras) {
-        extras.putInt(EXTRA_DEVICE_DURATION_POSITION, mDeviceDurationPosition);
-        extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION,
-                mDeviceEnergyConsumerPosition);
-        extras.putInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT,
-                mDeviceEnergyConsumerCount);
-        extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
-        extras.putInt(EXTRA_UID_DURATION_POSITION, mUidDurationPosition);
-        extras.putInt(EXTRA_UID_ENERGY_CONSUMERS_POSITION, mUidEnergyConsumerPosition);
-        extras.putInt(EXTRA_UID_ENERGY_CONSUMERS_COUNT, mUidEnergyConsumerCount);
-        extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
-        extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString());
-        extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString());
-        extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, mUidFormat.toString());
-    }
-
-    /**
-     * Retrieves elements of the stats array layout from <code>extras</code>
-     */
-    public void fromExtras(PersistableBundle extras) {
-        mDeviceDurationPosition = extras.getInt(EXTRA_DEVICE_DURATION_POSITION);
-        mDeviceEnergyConsumerPosition = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_POSITION);
-        mDeviceEnergyConsumerCount = extras.getInt(EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT);
-        mDevicePowerEstimatePosition = extras.getInt(EXTRA_DEVICE_POWER_POSITION);
-        mUidDurationPosition = extras.getInt(EXTRA_UID_DURATION_POSITION);
-        mUidEnergyConsumerPosition = extras.getInt(EXTRA_UID_ENERGY_CONSUMERS_POSITION);
-        mUidEnergyConsumerCount = extras.getInt(EXTRA_UID_ENERGY_CONSUMERS_COUNT);
-        mUidPowerEstimatePosition = extras.getInt(EXTRA_UID_POWER_POSITION);
-    }
-
     protected void putIntArray(PersistableBundle extras, String key, int[] array) {
         if (array == null) {
             return;
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java
similarity index 89%
rename from services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java
index f134aa8..6f6a7ff 100644
--- a/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/ScreenPowerStatsLayout.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
 import android.annotation.NonNull;
 import android.os.BatteryStats;
@@ -41,14 +41,40 @@
     private int mDeviceScreenDozePowerPosition;
     private int mUidTopActivityTimePosition;
 
-    ScreenPowerStatsLayout() {
+    public ScreenPowerStatsLayout(int energyConsumerCount, int displayCount) {
+        addDeviceScreenUsageDurationSection(displayCount);
+        addDeviceSectionEnergyConsumers(energyConsumerCount);
+        addDeviceSectionUsageDuration();
+        addDeviceSectionPowerEstimate();
+        addUidTopActivitiyDuration();
+        addUidSectionPowerEstimate();
     }
 
-    ScreenPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+    public ScreenPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
         super(descriptor);
+        PersistableBundle extras = descriptor.extras;
+        mDisplayCount = extras.getInt(EXTRA_DEVICE_SCREEN_COUNT, 1);
+        mDeviceScreenOnDurationPosition = extras.getInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION);
+        mDeviceBrightnessDurationPositions = extras.getIntArray(
+                EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS);
+        mDeviceScreenDozeDurationPosition = extras.getInt(EXTRA_DEVICE_DOZE_DURATION_POSITION);
+        mDeviceScreenDozePowerPosition = extras.getInt(EXTRA_DEVICE_DOZE_POWER_POSITION);
+        mUidTopActivityTimePosition = extras.getInt(EXTRA_UID_FOREGROUND_DURATION);
     }
 
-    void addDeviceScreenUsageDurationSection(int displayCount) {
+    @Override
+    public void toExtras(PersistableBundle extras) {
+        super.toExtras(extras);
+        extras.putInt(EXTRA_DEVICE_SCREEN_COUNT, mDisplayCount);
+        extras.putInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION, mDeviceScreenOnDurationPosition);
+        extras.putIntArray(EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS,
+                mDeviceBrightnessDurationPositions);
+        extras.putInt(EXTRA_DEVICE_DOZE_DURATION_POSITION, mDeviceScreenDozeDurationPosition);
+        extras.putInt(EXTRA_DEVICE_DOZE_POWER_POSITION, mDeviceScreenDozePowerPosition);
+        extras.putInt(EXTRA_UID_FOREGROUND_DURATION, mUidTopActivityTimePosition);
+    }
+
+    private void addDeviceScreenUsageDurationSection(int displayCount) {
         mDisplayCount = displayCount;
         mDeviceScreenOnDurationPosition = addDeviceSection(displayCount, "on");
         mDeviceBrightnessDurationPositions = new int[BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS];
@@ -60,7 +86,7 @@
     }
 
     @Override
-    public void addDeviceSectionPowerEstimate() {
+    protected void addDeviceSectionPowerEstimate() {
         super.addDeviceSectionPowerEstimate();
         // Used by AmbientDisplayPowerStatsProcessor
         mDeviceScreenDozePowerPosition = addDeviceSection(1, "doze-power", FLAG_HIDDEN);
@@ -127,7 +153,7 @@
         return stats[mDeviceScreenDozePowerPosition] / MILLI_TO_NANO_MULTIPLIER;
     }
 
-    void addUidTopActivitiyDuration() {
+    private void addUidTopActivitiyDuration() {
         mUidTopActivityTimePosition = addUidSection(1, "top");
     }
 
@@ -144,28 +170,4 @@
     public long getUidTopActivityDuration(long[] stats) {
         return stats[mUidTopActivityTimePosition];
     }
-
-    @Override
-    public void toExtras(PersistableBundle extras) {
-        super.toExtras(extras);
-        extras.putInt(EXTRA_DEVICE_SCREEN_COUNT, mDisplayCount);
-        extras.putInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION, mDeviceScreenOnDurationPosition);
-        extras.putIntArray(EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS,
-                mDeviceBrightnessDurationPositions);
-        extras.putInt(EXTRA_DEVICE_DOZE_DURATION_POSITION, mDeviceScreenDozeDurationPosition);
-        extras.putInt(EXTRA_DEVICE_DOZE_POWER_POSITION, mDeviceScreenDozePowerPosition);
-        extras.putInt(EXTRA_UID_FOREGROUND_DURATION, mUidTopActivityTimePosition);
-    }
-
-    @Override
-    public void fromExtras(PersistableBundle extras) {
-        super.fromExtras(extras);
-        mDisplayCount = extras.getInt(EXTRA_DEVICE_SCREEN_COUNT, 1);
-        mDeviceScreenOnDurationPosition = extras.getInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION);
-        mDeviceBrightnessDurationPositions = extras.getIntArray(
-                EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS);
-        mDeviceScreenDozeDurationPosition = extras.getInt(EXTRA_DEVICE_DOZE_DURATION_POSITION);
-        mDeviceScreenDozePowerPosition = extras.getInt(EXTRA_DEVICE_DOZE_POWER_POSITION);
-        mUidTopActivityTimePosition = extras.getInt(EXTRA_UID_FOREGROUND_DURATION);
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java
similarity index 72%
rename from services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java
index e66cd39..e8df3dd 100644
--- a/services/core/java/com/android/server/power/stats/SensorPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/SensorPowerStatsLayout.java
@@ -14,12 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
 import android.os.PersistableBundle;
 import android.util.Slog;
 import android.util.SparseIntArray;
 
+import com.android.internal.os.PowerStats;
+
+import java.util.Arrays;
+import java.util.Map;
+
 public class SensorPowerStatsLayout extends PowerStatsLayout {
     private static final String TAG = "SensorPowerStatsLayout";
     private static final String EXTRA_DEVICE_SENSOR_HANDLES = "dsh";
@@ -27,7 +32,48 @@
 
     private final SparseIntArray mSensorPositions = new SparseIntArray();
 
-    void addUidSensorSection(int handle, String label) {
+    public SensorPowerStatsLayout(Map<Integer, String> idToLabelMap) {
+        Integer[] keys = new Integer[idToLabelMap.size()];
+        idToLabelMap.keySet().toArray(keys);
+        Arrays.sort(keys);
+        for (int i = 0; i < keys.length; i++) {
+            addUidSensorSection(keys[i], idToLabelMap.get(keys[i]));
+        }
+        addUidSectionPowerEstimate();
+        addDeviceSectionPowerEstimate();
+    }
+
+    public SensorPowerStatsLayout(PowerStats.Descriptor descriptor) {
+        super(descriptor);
+
+        PersistableBundle extras = descriptor.extras;
+        int[] handlers = extras.getIntArray(EXTRA_DEVICE_SENSOR_HANDLES);
+        int[] uidDurationPositions = extras.getIntArray(EXTRA_UID_SENSOR_POSITIONS);
+
+        if (handlers != null && uidDurationPositions != null) {
+            for (int i = 0; i < handlers.length; i++) {
+                mSensorPositions.put(handlers[i], uidDurationPositions[i]);
+            }
+        }
+    }
+
+    @Override
+    public void toExtras(PersistableBundle extras) {
+        super.toExtras(extras);
+
+        int[] handlers = new int[mSensorPositions.size()];
+        int[] uidDurationPositions = new int[mSensorPositions.size()];
+
+        for (int i = 0; i < mSensorPositions.size(); i++) {
+            handlers[i] = mSensorPositions.keyAt(i);
+            uidDurationPositions[i] = mSensorPositions.valueAt(i);
+        }
+
+        extras.putIntArray(EXTRA_DEVICE_SENSOR_HANDLES, handlers);
+        extras.putIntArray(EXTRA_UID_SENSOR_POSITIONS, uidDurationPositions);
+    }
+
+    private void addUidSensorSection(int handle, String label) {
         mSensorPositions.put(handle, addUidSection(1, label, FLAG_OPTIONAL));
     }
 
@@ -50,32 +96,4 @@
         }
         stats[position] += durationMs;
     }
-
-    @Override
-    public void toExtras(PersistableBundle extras) {
-        super.toExtras(extras);
-
-        int[] handlers = new int[mSensorPositions.size()];
-        int[] uidDurationPositions = new int[mSensorPositions.size()];
-
-        for (int i = 0; i < mSensorPositions.size(); i++) {
-            handlers[i] = mSensorPositions.keyAt(i);
-            uidDurationPositions[i] = mSensorPositions.valueAt(i);
-        }
-
-        extras.putIntArray(EXTRA_DEVICE_SENSOR_HANDLES, handlers);
-        extras.putIntArray(EXTRA_UID_SENSOR_POSITIONS, uidDurationPositions);
-    }
-
-    @Override
-    public void fromExtras(PersistableBundle extras) {
-        super.fromExtras(extras);
-
-        int[] handlers = extras.getIntArray(EXTRA_DEVICE_SENSOR_HANDLES);
-        int[] uidDurationPositions = extras.getIntArray(EXTRA_UID_SENSOR_POSITIONS);
-
-        for (int i = 0; i < handlers.length; i++) {
-            mSensorPositions.put(handlers[i], uidDurationPositions[i]);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/format/WifiPowerStatsLayout.java
similarity index 93%
rename from services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
rename to services/core/java/com/android/server/power/stats/format/WifiPowerStatsLayout.java
index e2e8226..ce7ef12 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/format/WifiPowerStatsLayout.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.format;
 
 import android.annotation.NonNull;
 import android.os.PersistableBundle;
@@ -22,7 +22,6 @@
 import com.android.internal.os.PowerStats;
 
 public class WifiPowerStatsLayout extends PowerStatsLayout {
-    private static final String TAG = "WifiPowerStatsLayout";
     private static final int UNSPECIFIED = -1;
     private static final String EXTRA_POWER_REPORTING_SUPPORTED = "prs";
     private static final String EXTRA_DEVICE_RX_TIME_POSITION = "dt-rx";
@@ -54,14 +53,56 @@
     private int mUidScanTimePosition;
     private int mUidBatchScanTimePosition;
 
-    WifiPowerStatsLayout() {
+    public WifiPowerStatsLayout(int energyConsumerCount, boolean powerReportingSupported) {
+        addDeviceWifiActivity(powerReportingSupported);
+        addDeviceSectionEnergyConsumers(energyConsumerCount);
+        addUidNetworkStats();
+        addDeviceSectionUsageDuration();
+        addDeviceSectionPowerEstimate();
+        addUidSectionPowerEstimate();
     }
 
-    WifiPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+    public WifiPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
         super(descriptor);
+        PersistableBundle extras = descriptor.extras;
+        mPowerReportingSupported = extras.getBoolean(EXTRA_POWER_REPORTING_SUPPORTED);
+        mDeviceRxTimePosition = extras.getInt(EXTRA_DEVICE_RX_TIME_POSITION);
+        mDeviceTxTimePosition = extras.getInt(EXTRA_DEVICE_TX_TIME_POSITION);
+        mDeviceScanTimePosition = extras.getInt(EXTRA_DEVICE_SCAN_TIME_POSITION);
+        mDeviceBasicScanTimePosition = extras.getInt(EXTRA_DEVICE_BASIC_SCAN_TIME_POSITION);
+        mDeviceBatchedScanTimePosition = extras.getInt(EXTRA_DEVICE_BATCHED_SCAN_TIME_POSITION);
+        mDeviceIdleTimePosition = extras.getInt(EXTRA_DEVICE_IDLE_TIME_POSITION);
+        mDeviceActiveTimePosition = extras.getInt(EXTRA_DEVICE_ACTIVE_TIME_POSITION);
+        mUidRxBytesPosition = extras.getInt(EXTRA_UID_RX_BYTES_POSITION);
+        mUidTxBytesPosition = extras.getInt(EXTRA_UID_TX_BYTES_POSITION);
+        mUidRxPacketsPosition = extras.getInt(EXTRA_UID_RX_PACKETS_POSITION);
+        mUidTxPacketsPosition = extras.getInt(EXTRA_UID_TX_PACKETS_POSITION);
+        mUidScanTimePosition = extras.getInt(EXTRA_UID_SCAN_TIME_POSITION);
+        mUidBatchScanTimePosition = extras.getInt(EXTRA_UID_BATCH_SCAN_TIME_POSITION);
     }
 
-    void addDeviceWifiActivity(boolean powerReportingSupported) {
+    /**
+     * Copies the elements of the stats array layout into <code>extras</code>
+     */
+    public void toExtras(PersistableBundle extras) {
+        super.toExtras(extras);
+        extras.putBoolean(EXTRA_POWER_REPORTING_SUPPORTED, mPowerReportingSupported);
+        extras.putInt(EXTRA_DEVICE_RX_TIME_POSITION, mDeviceRxTimePosition);
+        extras.putInt(EXTRA_DEVICE_TX_TIME_POSITION, mDeviceTxTimePosition);
+        extras.putInt(EXTRA_DEVICE_SCAN_TIME_POSITION, mDeviceScanTimePosition);
+        extras.putInt(EXTRA_DEVICE_BASIC_SCAN_TIME_POSITION, mDeviceBasicScanTimePosition);
+        extras.putInt(EXTRA_DEVICE_BATCHED_SCAN_TIME_POSITION, mDeviceBatchedScanTimePosition);
+        extras.putInt(EXTRA_DEVICE_IDLE_TIME_POSITION, mDeviceIdleTimePosition);
+        extras.putInt(EXTRA_DEVICE_ACTIVE_TIME_POSITION, mDeviceActiveTimePosition);
+        extras.putInt(EXTRA_UID_RX_BYTES_POSITION, mUidRxBytesPosition);
+        extras.putInt(EXTRA_UID_TX_BYTES_POSITION, mUidTxBytesPosition);
+        extras.putInt(EXTRA_UID_RX_PACKETS_POSITION, mUidRxPacketsPosition);
+        extras.putInt(EXTRA_UID_TX_PACKETS_POSITION, mUidTxPacketsPosition);
+        extras.putInt(EXTRA_UID_SCAN_TIME_POSITION, mUidScanTimePosition);
+        extras.putInt(EXTRA_UID_BATCH_SCAN_TIME_POSITION, mUidBatchScanTimePosition);
+    }
+
+    private void addDeviceWifiActivity(boolean powerReportingSupported) {
         mPowerReportingSupported = powerReportingSupported;
         if (mPowerReportingSupported) {
             mDeviceActiveTimePosition = UNSPECIFIED;
@@ -80,7 +121,7 @@
         mDeviceBatchedScanTimePosition = addDeviceSection(1, "batched-scan", FLAG_OPTIONAL);
     }
 
-    void addUidNetworkStats() {
+    private void addUidNetworkStats() {
         mUidRxPacketsPosition = addUidSection(1, "rx-pkts");
         mUidRxBytesPosition = addUidSection(1, "rx-B");
         mUidTxPacketsPosition = addUidSection(1, "tx-pkts");
@@ -196,46 +237,4 @@
     public long getUidBatchedScanTime(long[] stats) {
         return stats[mUidBatchScanTimePosition];
     }
-
-    /**
-     * Copies the elements of the stats array layout into <code>extras</code>
-     */
-    public void toExtras(PersistableBundle extras) {
-        super.toExtras(extras);
-        extras.putBoolean(EXTRA_POWER_REPORTING_SUPPORTED, mPowerReportingSupported);
-        extras.putInt(EXTRA_DEVICE_RX_TIME_POSITION, mDeviceRxTimePosition);
-        extras.putInt(EXTRA_DEVICE_TX_TIME_POSITION, mDeviceTxTimePosition);
-        extras.putInt(EXTRA_DEVICE_SCAN_TIME_POSITION, mDeviceScanTimePosition);
-        extras.putInt(EXTRA_DEVICE_BASIC_SCAN_TIME_POSITION, mDeviceBasicScanTimePosition);
-        extras.putInt(EXTRA_DEVICE_BATCHED_SCAN_TIME_POSITION, mDeviceBatchedScanTimePosition);
-        extras.putInt(EXTRA_DEVICE_IDLE_TIME_POSITION, mDeviceIdleTimePosition);
-        extras.putInt(EXTRA_DEVICE_ACTIVE_TIME_POSITION, mDeviceActiveTimePosition);
-        extras.putInt(EXTRA_UID_RX_BYTES_POSITION, mUidRxBytesPosition);
-        extras.putInt(EXTRA_UID_TX_BYTES_POSITION, mUidTxBytesPosition);
-        extras.putInt(EXTRA_UID_RX_PACKETS_POSITION, mUidRxPacketsPosition);
-        extras.putInt(EXTRA_UID_TX_PACKETS_POSITION, mUidTxPacketsPosition);
-        extras.putInt(EXTRA_UID_SCAN_TIME_POSITION, mUidScanTimePosition);
-        extras.putInt(EXTRA_UID_BATCH_SCAN_TIME_POSITION, mUidBatchScanTimePosition);
-    }
-
-    /**
-     * Retrieves elements of the stats array layout from <code>extras</code>
-     */
-    public void fromExtras(PersistableBundle extras) {
-        super.fromExtras(extras);
-        mPowerReportingSupported = extras.getBoolean(EXTRA_POWER_REPORTING_SUPPORTED);
-        mDeviceRxTimePosition = extras.getInt(EXTRA_DEVICE_RX_TIME_POSITION);
-        mDeviceTxTimePosition = extras.getInt(EXTRA_DEVICE_TX_TIME_POSITION);
-        mDeviceScanTimePosition = extras.getInt(EXTRA_DEVICE_SCAN_TIME_POSITION);
-        mDeviceBasicScanTimePosition = extras.getInt(EXTRA_DEVICE_BASIC_SCAN_TIME_POSITION);
-        mDeviceBatchedScanTimePosition = extras.getInt(EXTRA_DEVICE_BATCHED_SCAN_TIME_POSITION);
-        mDeviceIdleTimePosition = extras.getInt(EXTRA_DEVICE_IDLE_TIME_POSITION);
-        mDeviceActiveTimePosition = extras.getInt(EXTRA_DEVICE_ACTIVE_TIME_POSITION);
-        mUidRxBytesPosition = extras.getInt(EXTRA_UID_RX_BYTES_POSITION);
-        mUidTxBytesPosition = extras.getInt(EXTRA_UID_TX_BYTES_POSITION);
-        mUidRxPacketsPosition = extras.getInt(EXTRA_UID_RX_PACKETS_POSITION);
-        mUidTxPacketsPosition = extras.getInt(EXTRA_UID_TX_PACKETS_POSITION);
-        mUidScanTimePosition = extras.getInt(EXTRA_UID_SCAN_TIME_POSITION);
-        mUidBatchScanTimePosition = extras.getInt(EXTRA_UID_BATCH_SCAN_TIME_POSITION);
-    }
 }
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
similarity index 97%
rename from services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
rename to services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
index 674b4bc..a4758dd 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStats.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.DurationMillisLong;
@@ -33,7 +33,7 @@
 import com.android.internal.os.PowerStats;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.power.stats.AggregatedPowerStatsConfig.PowerComponent;
+import com.android.server.power.stats.processor.AggregatedPowerStatsConfig.PowerComponent;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -143,7 +143,8 @@
         }
     }
 
-    List<ClockUpdate> getClockUpdates() {
+    // TODO - DO NOT SUBMIT public
+    public List<ClockUpdate> getClockUpdates() {
         return mClockUpdates;
     }
 
@@ -274,7 +275,8 @@
                         int powerComponentId = parser.getAttributeInt(null,
                                 PowerComponentAggregatedPowerStats.XML_ATTR_ID);
 
-                        PowerComponentAggregatedPowerStats powerComponentStats =
+                        PowerComponentAggregatedPowerStats
+                                powerComponentStats =
                                 stats.getPowerComponentStats(powerComponentId);
                         if (powerComponentStats == null) {
                             PowerComponent powerComponent =
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsConfig.java
similarity index 95%
rename from services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
rename to services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsConfig.java
index ec12228..eaeda43 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsConfig.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -32,7 +32,7 @@
  * WiFi, etc).  Also, it determines which states are tracked globally and which ones on a per-UID
  * basis.
  */
-public class AggregatedPowerStatsConfig {
+class AggregatedPowerStatsConfig {
     public static final int STATE_POWER = 0;
     public static final int STATE_SCREEN = 1;
     public static final int STATE_PROCESS_STATE = 2;
@@ -70,7 +70,7 @@
     /**
      * Configuration for a give power component (CPU, WiFi, etc)
      */
-    public static class PowerComponent {
+    static class PowerComponent {
         private final int mPowerComponentId;
         private @TrackedState int[] mTrackedDeviceStates;
         private @TrackedState int[] mTrackedUidStates;
@@ -174,7 +174,7 @@
      * standard power component IDs, e.g. {@link BatteryConsumer#POWER_COMPONENT_CPU}, or
      * a custom power component.
      */
-    public PowerComponent trackPowerComponent(int powerComponentId) {
+    PowerComponent trackPowerComponent(int powerComponentId) {
         PowerComponent builder = new PowerComponent(powerComponentId);
         mPowerComponents.add(builder);
         return builder;
@@ -185,7 +185,7 @@
      * of a different power component.  The tracked states will be the same as the parent
      * component's.
      */
-    public PowerComponent trackPowerComponent(int powerComponentId,
+    PowerComponent trackPowerComponent(int powerComponentId,
             int parentPowerComponentId) {
         PowerComponent parent = null;
         for (int i = 0; i < mPowerComponents.size(); i++) {
@@ -211,7 +211,7 @@
      * Creates a configuration for custom power components, which are yet to be discovered
      * dynamically through the integration with PowerStatsService.
      */
-    public PowerComponent trackCustomPowerComponents(
+    PowerComponent trackCustomPowerComponents(
             Supplier<PowerStatsProcessor> processorFactory) {
         mCustomPowerStatsProcessorFactory = processorFactory;
         mCustomPowerComponent = new PowerComponent(BatteryConsumer.POWER_COMPONENT_ANY);
@@ -221,7 +221,7 @@
     /**
      * Returns configurations for all registered or dynamically discovered power components.
      */
-    public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() {
+    List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() {
         return mPowerComponents;
     }
 
@@ -230,7 +230,7 @@
      * integration with PowerStatsService.
      */
     @Nullable
-    public PowerComponent createPowerComponent(int powerComponentId) {
+    PowerComponent createPowerComponent(int powerComponentId) {
         if (mCustomPowerComponent == null) {
             return null;
         }
diff --git a/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsSection.java b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsSection.java
new file mode 100644
index 0000000..4a9730c
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/processor/AggregatedPowerStatsSection.java
@@ -0,0 +1,72 @@
+/*
+ * 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.server.power.stats.processor;
+
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.power.stats.PowerStatsSpan;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class AggregatedPowerStatsSection extends PowerStatsSpan.Section {
+    public static final String TYPE = "aggregated-power-stats";
+
+    private final AggregatedPowerStats mAggregatedPowerStats;
+
+    AggregatedPowerStatsSection(AggregatedPowerStats aggregatedPowerStats) {
+        super(TYPE);
+        mAggregatedPowerStats = aggregatedPowerStats;
+    }
+
+    public AggregatedPowerStats getAggregatedPowerStats() {
+        return mAggregatedPowerStats;
+    }
+
+    @Override
+    public void write(TypedXmlSerializer serializer) throws IOException {
+        mAggregatedPowerStats.writeXml(serializer);
+    }
+
+    @Override
+    public void dump(IndentingPrintWriter ipw) {
+        mAggregatedPowerStats.dump(ipw);
+    }
+
+    static class Reader implements PowerStatsSpan.SectionReader {
+        private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+
+        Reader(AggregatedPowerStatsConfig config) {
+            mAggregatedPowerStatsConfig = config;
+        }
+
+        @Override
+        public String getType() {
+            return TYPE;
+        }
+
+        @Override
+        public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return new AggregatedPowerStatsSection(
+                    AggregatedPowerStats.createFromXml(parser, mAggregatedPowerStatsConfig));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
similarity index 86%
rename from services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
index a42929f..32dfdf9 100644
--- a/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessor.java
@@ -13,24 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.os.BatteryConsumer;
 import android.os.PersistableBundle;
 
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.AmbientDisplayPowerStatsLayout;
+import com.android.server.power.stats.format.ScreenPowerStatsLayout;
 
-public class AmbientDisplayPowerStatsProcessor extends PowerStatsProcessor {
-    private final PowerStatsLayout mStatsLayout;
+class AmbientDisplayPowerStatsProcessor extends PowerStatsProcessor {
+    private final AmbientDisplayPowerStatsLayout mStatsLayout;
     private final PowerStats.Descriptor mDescriptor;
     private final long[] mTmpDeviceStats;
     private PowerStats.Descriptor mScreenPowerStatsDescriptor;
     private ScreenPowerStatsLayout mScreenPowerStatsLayout;
     private long[] mTmpScreenStats;
 
-    public AmbientDisplayPowerStatsProcessor() {
-        mStatsLayout = new PowerStatsLayout();
-        mStatsLayout.addDeviceSectionPowerEstimate();
+    AmbientDisplayPowerStatsProcessor() {
+        mStatsLayout = new AmbientDisplayPowerStatsLayout();
         PersistableBundle extras = new PersistableBundle();
         mStatsLayout.toExtras(extras);
         mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
diff --git a/services/core/java/com/android/server/power/stats/AudioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/AudioPowerStatsProcessor.java
similarity index 82%
rename from services/core/java/com/android/server/power/stats/AudioPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/AudioPowerStatsProcessor.java
index a48f162..ad1b4a7 100644
--- a/services/core/java/com/android/server/power/stats/AudioPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/AudioPowerStatsProcessor.java
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 
 import com.android.internal.os.PowerProfile;
+import com.android.server.power.stats.PowerStatsUidResolver;
 
-public class AudioPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
-    public AudioPowerStatsProcessor(PowerProfile powerProfile,
+class AudioPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
+    AudioPowerStatsProcessor(PowerProfile powerProfile,
             PowerStatsUidResolver uidResolver) {
         super(BatteryConsumer.POWER_COMPONENT_AUDIO, uidResolver,
                 powerProfile.getAveragePower(PowerProfile.POWER_AUDIO));
diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java
similarity index 95%
rename from services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java
index 393fa39..e45a707 100644
--- a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.annotation.IntDef;
 import android.os.BatteryStats;
@@ -22,6 +22,9 @@
 import android.os.Process;
 
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.UsageBasedPowerEstimator;
+import com.android.server.power.stats.format.BinaryStatePowerStatsLayout;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -115,6 +118,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/BluetoothPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java
similarity index 97%
rename from services/core/java/com/android/server/power/stats/BluetoothPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java
index 077b057..4c1a0db 100644
--- a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/BluetoothPowerStatsProcessor.java
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.UsageBasedPowerEstimator;
+import com.android.server.power.stats.format.BluetoothPowerStatsLayout;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class BluetoothPowerStatsProcessor extends PowerStatsProcessor {
-    private static final String TAG = "BluetoothPowerStatsProcessor";
-
+class BluetoothPowerStatsProcessor extends PowerStatsProcessor {
     private final UsageBasedPowerEstimator mRxPowerEstimator;
     private final UsageBasedPowerEstimator mTxPowerEstimator;
     private final UsageBasedPowerEstimator mIdlePowerEstimator;
@@ -37,7 +37,7 @@
     private long[] mTmpDeviceStatsArray;
     private long[] mTmpUidStatsArray;
 
-    public BluetoothPowerStatsProcessor(PowerProfile powerProfile) {
+    BluetoothPowerStatsProcessor(PowerProfile powerProfile) {
         mRxPowerEstimator = new UsageBasedPowerEstimator(
                 powerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX));
         mTxPowerEstimator = new UsageBasedPowerEstimator(
diff --git a/services/core/java/com/android/server/power/stats/CameraPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CameraPowerStatsProcessor.java
similarity index 82%
rename from services/core/java/com/android/server/power/stats/CameraPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/CameraPowerStatsProcessor.java
index 15c3eb8..8309061 100644
--- a/services/core/java/com/android/server/power/stats/CameraPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/CameraPowerStatsProcessor.java
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 
 import com.android.internal.os.PowerProfile;
+import com.android.server.power.stats.PowerStatsUidResolver;
 
-public class CameraPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
-    public CameraPowerStatsProcessor(PowerProfile powerProfile,
+class CameraPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
+    CameraPowerStatsProcessor(PowerProfile powerProfile,
             PowerStatsUidResolver uidResolver) {
         super(BatteryConsumer.POWER_COMPONENT_CAMERA, uidResolver,
                 powerProfile.getAveragePower(PowerProfile.POWER_CAMERA));
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
similarity index 97%
rename from services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
index 6da0a8f..5f7a3da 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/CpuPowerStatsProcessor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.util.ArraySet;
 import android.util.Log;
@@ -22,13 +22,14 @@
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.CpuPowerStatsLayout;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-public class CpuPowerStatsProcessor extends PowerStatsProcessor {
+class CpuPowerStatsProcessor extends PowerStatsProcessor {
     private static final String TAG = "CpuPowerStatsProcessor";
 
     private static final double HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
@@ -72,7 +73,7 @@
     // Temp array for retrieval of UID power stats, to avoid repeated allocations
     private long[] mTmpUidStatsArray;
 
-    public CpuPowerStatsProcessor(PowerProfile powerProfile, CpuScalingPolicies scalingPolicies) {
+    CpuPowerStatsProcessor(PowerProfile powerProfile, CpuScalingPolicies scalingPolicies) {
         mCpuScalingPolicies = scalingPolicies;
         mCpuScalingStepCount = scalingPolicies.getScalingStepCount();
         mScalingStepToCluster = new int[mCpuScalingStepCount];
@@ -104,8 +105,7 @@
         }
 
         mLastUsedDescriptor = descriptor;
-        mStatsLayout = new CpuPowerStatsLayout();
-        mStatsLayout.fromExtras(descriptor.extras);
+        mStatsLayout = new CpuPowerStatsLayout(descriptor);
 
         mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
         mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
@@ -138,7 +138,7 @@
     }
 
     @Override
-    public void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+    void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
         if (stats.getPowerStatsDescriptor() == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java
similarity index 94%
rename from services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java
index a86242a..76adc47 100644
--- a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsProcessor.java
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.EnergyConsumerPowerStatsLayout;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor {
+class CustomEnergyConsumerPowerStatsProcessor extends PowerStatsProcessor {
     private static final EnergyConsumerPowerStatsLayout sLayout =
             new EnergyConsumerPowerStatsLayout();
     private long[] mTmpDeviceStatsArray;
diff --git a/services/core/java/com/android/server/power/stats/FlashlightPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/FlashlightPowerStatsProcessor.java
similarity index 82%
rename from services/core/java/com/android/server/power/stats/FlashlightPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/FlashlightPowerStatsProcessor.java
index f7216c9..b0bef69 100644
--- a/services/core/java/com/android/server/power/stats/FlashlightPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/FlashlightPowerStatsProcessor.java
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 
 import com.android.internal.os.PowerProfile;
+import com.android.server.power.stats.PowerStatsUidResolver;
 
-public class FlashlightPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
-    public FlashlightPowerStatsProcessor(PowerProfile powerProfile,
+class FlashlightPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
+    FlashlightPowerStatsProcessor(PowerProfile powerProfile,
             PowerStatsUidResolver uidResolver) {
         super(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, uidResolver,
                 powerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT));
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/GnssPowerStatsProcessor.java
similarity index 78%
rename from services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/GnssPowerStatsProcessor.java
index 572bde9..f1e3e90 100644
--- a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/GnssPowerStatsProcessor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.location.GnssSignalQuality;
 import android.os.BatteryConsumer;
@@ -23,21 +23,24 @@
 
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.UsageBasedPowerEstimator;
+import com.android.server.power.stats.format.GnssPowerStatsLayout;
 
 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];
+class GnssPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
     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) {
+    GnssPowerStatsProcessor(PowerProfile powerProfile, PowerStatsUidResolver uidResolver) {
         super(BatteryConsumer.POWER_COMPONENT_GNSS, uidResolver,
                 powerProfile.getAveragePower(PowerProfile.POWER_GPS_ON),
                 sStatsLayout);
@@ -55,20 +58,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/power/stats/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java
similarity index 96%
rename from services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java
index dcce562..b4c40de8 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessor.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.os.BatteryStats;
 import android.telephony.CellSignalStrength;
@@ -26,13 +26,15 @@
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
 import com.android.internal.power.ModemPowerProfile;
+import com.android.server.power.stats.UsageBasedPowerEstimator;
+import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-public class MobileRadioPowerStatsProcessor extends PowerStatsProcessor {
-    private static final String TAG = "MobileRadioPowerStatsProcessor";
+class MobileRadioPowerStatsProcessor extends PowerStatsProcessor {
+    private static final String TAG = "MobileRadioPowerStats";
     private static final boolean DEBUG = false;
 
     private static final int NUM_SIGNAL_STRENGTH_LEVELS =
@@ -61,7 +63,7 @@
     private long[] mTmpStateStatsArray;
     private long[] mTmpUidStatsArray;
 
-    public MobileRadioPowerStatsProcessor(PowerProfile powerProfile) {
+    MobileRadioPowerStatsProcessor(PowerProfile powerProfile) {
         final double sleepDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa(
                 PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
                 Double.NaN);
@@ -101,7 +103,7 @@
                     ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
             for (int freqRange = 0; freqRange < freqCount; freqRange++) {
                 mRxTxPowerEstimators.put(
-                        MobileRadioPowerStatsCollector.makeStateKey(rat, freqRange),
+                        MobileRadioPowerStatsLayout.makeStateKey(rat, freqRange),
                         buildRxTxPowerEstimators(powerProfile, rat, freqRange));
             }
         }
@@ -114,8 +116,7 @@
                 ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat, freqRange, IGNORE);
         double rxDrainRateMa = powerProfile.getAverageBatteryDrainOrDefaultMa(rxKey, Double.NaN);
         if (Double.isNaN(rxDrainRateMa)) {
-            Log.w(TAG, "Unavailable Power Profile constant for key 0x"
-                    + Long.toHexString(rxKey));
+            Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(rxKey));
             rxDrainRateMa = 0;
         }
         estimators.mRxPowerEstimator = new UsageBasedPowerEstimator(rxDrainRateMa);
diff --git a/services/core/java/com/android/server/power/stats/processor/MultiStatePowerAttributor.java b/services/core/java/com/android/server/power/stats/processor/MultiStatePowerAttributor.java
new file mode 100644
index 0000000..2ba4a52
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/processor/MultiStatePowerAttributor.java
@@ -0,0 +1,310 @@
+/*
+ * 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.power.stats.processor;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.os.BatteryConsumer;
+import android.os.BatteryUsageStats;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.CpuScalingPolicies;
+import com.android.internal.os.PowerProfile;
+import com.android.server.power.stats.PowerAttributor;
+import com.android.server.power.stats.PowerStatsSpan;
+import com.android.server.power.stats.PowerStatsStore;
+import com.android.server.power.stats.PowerStatsUidResolver;
+
+import java.util.List;
+
+public class MultiStatePowerAttributor implements PowerAttributor {
+    private static final String TAG = "MultiStatePowerAttributor";
+
+    private final PowerStatsStore mPowerStatsStore;
+    private final PowerStatsExporter mPowerStatsExporter;
+    private final PowerStatsAggregator mPowerStatsAggregator;
+    private final SparseBooleanArray mPowerStatsExporterEnabled = new SparseBooleanArray();
+
+    // TODO(b/346371828): remove dependency on PowerStatsUidResolver. At the time of power
+    // attribution isolates UIDs are supposed to be long forgotten.
+    public MultiStatePowerAttributor(Context context, PowerStatsStore powerStatsStore,
+            @NonNull PowerProfile powerProfile, @NonNull CpuScalingPolicies cpuScalingPolicies,
+            @NonNull PowerStatsUidResolver powerStatsUidResolver) {
+        this(powerStatsStore, new PowerStatsAggregator(
+                createAggregatedPowerStatsConfig(context, powerProfile, cpuScalingPolicies,
+                        powerStatsUidResolver)));
+    }
+
+    @VisibleForTesting
+    MultiStatePowerAttributor(PowerStatsStore powerStatsStore,
+            PowerStatsAggregator powerStatsAggregator) {
+        mPowerStatsStore = powerStatsStore;
+        mPowerStatsAggregator = powerStatsAggregator;
+        mPowerStatsStore.addSectionReader(
+                new AggregatedPowerStatsSection.Reader(mPowerStatsAggregator.getConfig()));
+        mPowerStatsExporter = new PowerStatsExporter(mPowerStatsStore, mPowerStatsAggregator);
+    }
+
+    private static AggregatedPowerStatsConfig createAggregatedPowerStatsConfig(Context context,
+            PowerProfile powerProfile, CpuScalingPolicies cpuScalingPolicies,
+            PowerStatsUidResolver powerStatsUidResolver) {
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new CpuPowerStatsProcessor(powerProfile, cpuScalingPolicies));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SCREEN)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .setProcessorSupplier(
+                        () -> new ScreenPowerStatsProcessor(powerProfile));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+                        BatteryConsumer.POWER_COMPONENT_SCREEN)
+                .setProcessorSupplier(AmbientDisplayPowerStatsProcessor::new);
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new MobileRadioPowerStatsProcessor(powerProfile));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_PHONE,
+                        BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+                .setProcessorSupplier(PhoneCallPowerStatsProcessor::new);
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_WIFI)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new WifiPowerStatsProcessor(powerProfile));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new BluetoothPowerStatsProcessor(powerProfile));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AUDIO)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new AudioPowerStatsProcessor(powerProfile, powerStatsUidResolver));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_VIDEO)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new VideoPowerStatsProcessor(powerProfile, powerStatsUidResolver));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new FlashlightPowerStatsProcessor(powerProfile,
+                                powerStatsUidResolver));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CAMERA)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new CameraPowerStatsProcessor(powerProfile, powerStatsUidResolver));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_GNSS)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new GnssPowerStatsProcessor(powerProfile, powerStatsUidResolver));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SENSORS)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessorSupplier(() -> new SensorPowerStatsProcessor(
+                        () -> context.getSystemService(SensorManager.class)));
+
+        config.trackCustomPowerComponents(CustomEnergyConsumerPowerStatsProcessor::new)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
+        return config;
+    }
+
+    /**
+     * Marks the specified power component as supported by this PowerAttributor
+     */
+    public void setPowerComponentSupported(@BatteryConsumer.PowerComponentId int powerComponentId,
+            boolean enabled) {
+        mPowerStatsExporterEnabled.put(powerComponentId, enabled);
+        mPowerStatsExporter.setPowerComponentEnabled(powerComponentId, enabled);
+    }
+
+    @Override
+    public boolean isPowerComponentSupported(
+            @BatteryConsumer.PowerComponentId int powerComponentId) {
+        return mPowerStatsExporterEnabled.get(powerComponentId);
+    }
+
+    @Override
+    public void estimatePowerConsumption(BatteryUsageStats.Builder batteryUsageStatsBuilder,
+            BatteryStatsHistory batteryHistory, long monotonicStartTime, long monotonicEndTime) {
+        mPowerStatsExporter.exportAggregatedPowerStats(batteryUsageStatsBuilder, batteryHistory,
+                monotonicStartTime, monotonicEndTime);
+    }
+
+    @Override
+    public void dumpEstimatedPowerConsumption(IndentingPrintWriter ipw,
+            BatteryStatsHistory batteryStatsHistory,
+            long startTime, long endTime) {
+        mPowerStatsAggregator.aggregatePowerStats(batteryStatsHistory, startTime, endTime,
+                stats -> {
+                    // Create a PowerStatsSpan for consistency of the textual output
+                    PowerStatsSpan span = createPowerStatsSpan(stats);
+                    if (span != null) {
+                        span.dump(ipw);
+                    }
+                });
+    }
+
+    @Override
+    public long storeEstimatedPowerConsumption(BatteryStatsHistory batteryStatsHistory,
+            long startTime, long endTimeMs) {
+        long[] lastSavedMonotonicTime = new long[1];
+        mPowerStatsAggregator.aggregatePowerStats(batteryStatsHistory, startTime, endTimeMs,
+                stats -> {
+                    storeAggregatedPowerStats(stats);
+                    lastSavedMonotonicTime[0] = stats.getStartTime() + stats.getDuration();
+                });
+        return lastSavedMonotonicTime[0];
+    }
+
+    @VisibleForTesting
+    void storeAggregatedPowerStats(AggregatedPowerStats stats) {
+        PowerStatsSpan span = createPowerStatsSpan(stats);
+        if (span == null) {
+            return;
+        }
+        mPowerStatsStore.storePowerStatsSpan(span);
+    }
+
+    private static PowerStatsSpan createPowerStatsSpan(AggregatedPowerStats stats) {
+        List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates();
+        if (clockUpdates.isEmpty()) {
+            Slog.w(TAG, "No clock updates in aggregated power stats " + stats);
+            return null;
+        }
+
+        long monotonicTime = clockUpdates.get(0).monotonicTime;
+        long durationSum = 0;
+        PowerStatsSpan span = new PowerStatsSpan(monotonicTime);
+        for (int i = 0; i < clockUpdates.size(); i++) {
+            AggregatedPowerStats.ClockUpdate clockUpdate = clockUpdates.get(i);
+            long duration;
+            if (i == clockUpdates.size() - 1) {
+                duration = stats.getDuration() - durationSum;
+            } else {
+                duration = clockUpdate.monotonicTime - monotonicTime;
+            }
+            span.addTimeFrame(clockUpdate.monotonicTime, clockUpdate.currentTime, duration);
+            monotonicTime = clockUpdate.monotonicTime;
+            durationSum += duration;
+        }
+
+        span.addSection(new AggregatedPowerStatsSection(stats));
+        return span;
+    }
+
+    @Override
+    public long getLastSavedEstimatesPowerConsumptionTimestamp() {
+        long timestamp = -1;
+        for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) {
+            if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
+                for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) {
+                    long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration;
+                    if (endMonotonicTime > timestamp) {
+                        timestamp = endMonotonicTime;
+                    }
+                }
+            }
+        }
+        return timestamp;
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/MultiStateStats.java b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java
similarity index 94%
rename from services/core/java/com/android/server/power/stats/MultiStateStats.java
rename to services/core/java/com/android/server/power/stats/processor/MultiStateStats.java
index c3a0aeb..28474a5 100644
--- a/services/core/java/com/android/server/power/stats/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/MultiStateStats.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.util.Slog;
 
@@ -37,7 +37,7 @@
  * CPU residency, Network packet counts etc.  All metrics must be represented as <code>long</code>
  * values;
  */
-public class MultiStateStats {
+class MultiStateStats {
     private static final String TAG = "MultiStateStats";
 
     private static final String XML_TAG_STATS = "stats";
@@ -47,12 +47,12 @@
      * A set of states, e.g. on-battery, screen-on, procstate.  The state values are integers
      * from 0 to States.mLabels.length
      */
-    public static class States {
+    static class States {
         final String mName;
         final boolean mTracked;
         final String[] mLabels;
 
-        public States(String name, boolean tracked, String... labels) {
+        States(String name, boolean tracked, String... labels) {
             mName = name;
             mTracked = tracked;
             mLabels = labels;
@@ -121,7 +121,7 @@
      * Factory for MultiStateStats containers. All generated containers retain their connection
      * to the Factory and the corresponding configuration.
      */
-    public static class Factory {
+    static class Factory {
         private static final int INVALID_SERIAL_STATE = -1;
         final int mDimensionCount;
         final States[] mStates;
@@ -147,7 +147,7 @@
         final int[] mCompositeToSerialState;
         final int mSerialStateCount;
 
-        public Factory(int dimensionCount, States... states) {
+        Factory(int dimensionCount, States... states) {
             mDimensionCount = dimensionCount;
             mStates = states;
 
@@ -250,7 +250,7 @@
         /**
          * Allocates a new stats container using this Factory's configuration.
          */
-        public MultiStateStats create() {
+        MultiStateStats create() {
             return new MultiStateStats(this, mDimensionCount);
         }
 
@@ -293,16 +293,16 @@
     private int mCompositeState;
     private boolean mTracking;
 
-    public MultiStateStats(Factory factory, int dimensionCount) {
+    MultiStateStats(Factory factory, int dimensionCount) {
         this.mFactory = factory;
         mCounter = new LongArrayMultiStateCounter(factory.mSerialStateCount, dimensionCount);
     }
 
-    public int getDimensionCount() {
+    int getDimensionCount() {
         return mFactory.mDimensionCount;
     }
 
-    public States[] getStates() {
+    States[] getStates() {
         return mFactory.mStates;
     }
 
@@ -310,7 +310,7 @@
      * Copies time-in-state and timestamps from the supplied prototype. Does not
      * copy accumulated counts.
      */
-    public void copyStatesFrom(MultiStateStats otherStats) {
+    void copyStatesFrom(MultiStateStats otherStats) {
         mCounter.copyStatesFrom(otherStats.mCounter);
     }
 
@@ -322,7 +322,7 @@
      * @param state       The new value of the state (e.g. 0 or 1 for "on-battery")
      * @param timestampMs The time when the state change occurred
      */
-    public void setState(int stateIndex, int state, long timestampMs) {
+    void setState(int stateIndex, int state, long timestampMs) {
         if (!mTracking) {
             mCounter.updateValues(new long[mCounter.getArrayLength()], timestampMs);
             mTracking = true;
@@ -335,7 +335,7 @@
      * Adds the delta to the metrics.  The number of values must correspond to the dimension count
      * supplied to the Factory constructor
      */
-    public void increment(long[] values, long timestampMs) {
+    void increment(long[] values, long timestampMs) {
         mCounter.incrementValues(values, timestampMs);
         mTracking = true;
     }
@@ -343,21 +343,21 @@
     /**
      * Returns accumulated stats for the specified composite state.
      */
-    public void getStats(long[] outValues, int[] states) {
+    void getStats(long[] outValues, int[] states) {
         mCounter.getCounts(outValues, mFactory.getSerialState(states));
     }
 
     /**
      * Updates the stats values for the provided combination of states.
      */
-    public void setStats(int[] states, long[] values) {
+    void setStats(int[] states, long[] values) {
         mCounter.setValues(mFactory.getSerialState(states), values);
     }
 
     /**
      * Resets the counters.
      */
-    public void reset() {
+    void reset() {
         mCounter.reset();
         mTracking = false;
     }
@@ -365,7 +365,7 @@
     /**
      * Stores contents in an XML doc.
      */
-    public void writeXml(TypedXmlSerializer serializer) throws IOException {
+    void writeXml(TypedXmlSerializer serializer) throws IOException {
         long[] tmpArray = new long[mCounter.getArrayLength()];
 
         try {
@@ -420,8 +420,7 @@
      * Populates the object with contents in an XML doc. The parser is expected to be
      * positioned on the opening tag of the corresponding element.
      */
-    public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException,
-            IOException {
+    boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException, IOException {
         String outerTag = parser.getName();
         long[] tmpArray = new long[mCounter.getArrayLength()];
         int eventType = parser.getEventType();
diff --git a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java
similarity index 88%
rename from services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java
index ec23fa0..3957ae0 100644
--- a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessor.java
@@ -13,24 +13,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.os.BatteryConsumer;
 import android.os.PersistableBundle;
 
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
+import com.android.server.power.stats.format.PhoneCallPowerStatsLayout;
 
-public class PhoneCallPowerStatsProcessor extends PowerStatsProcessor {
-    private final PowerStatsLayout mStatsLayout;
+class PhoneCallPowerStatsProcessor extends PowerStatsProcessor {
+    private final PhoneCallPowerStatsLayout mStatsLayout;
     private final PowerStats.Descriptor mDescriptor;
     private final long[] mTmpDeviceStats;
     private PowerStats.Descriptor mMobileRadioStatsDescriptor;
     private MobileRadioPowerStatsLayout mMobileRadioStatsLayout;
     private long[] mTmpMobileRadioDeviceStats;
 
-    public PhoneCallPowerStatsProcessor() {
-        mStatsLayout = new PowerStatsLayout();
-        mStatsLayout.addDeviceSectionPowerEstimate();
+    PhoneCallPowerStatsProcessor() {
+        mStatsLayout = new PhoneCallPowerStatsLayout();
         PersistableBundle extras = new PersistableBundle();
         mStatsLayout.toExtras(extras);
         mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_PHONE,
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
similarity index 98%
rename from services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
rename to services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
index a92a6fd3..d04c5ba 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerComponentAggregatedPowerStats.java
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
-
-import static com.android.server.power.stats.MultiStateStats.STATE_DOES_NOT_EXIST;
+package com.android.server.power.stats.processor;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -147,7 +145,8 @@
 
         int uidStateId = MultiStateStats.States
                 .findTrackedStateByName(mUidStateConfig, mDeviceStateConfig[stateId].getName());
-        if (uidStateId != STATE_DOES_NOT_EXIST && mUidStateConfig[uidStateId].isTracked()) {
+        if (uidStateId != MultiStateStats.STATE_DOES_NOT_EXIST
+                && mUidStateConfig[uidStateId].isTracked()) {
             for (int i = mUidStats.size() - 1; i >= 0; i--) {
                 PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i);
                 if (uidStats.stats == null) {
@@ -271,7 +270,7 @@
                 if (mUidStateConfig[stateId].isTracked()) {
                     int deviceStateId = MultiStateStats.States.findTrackedStateByName(
                             mDeviceStateConfig, mUidStateConfig[stateId].getName());
-                    if (deviceStateId != STATE_DOES_NOT_EXIST
+                    if (deviceStateId != MultiStateStats.STATE_DOES_NOT_EXIST
                             && mDeviceStateConfig[deviceStateId].isTracked()) {
                         uidStats.states[stateId] = mDeviceStates[deviceStateId];
                     }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
similarity index 92%
rename from services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
rename to services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
index c734f68..32c1056 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java
@@ -13,13 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.annotation.NonNull;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.util.SparseBooleanArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.BatteryStatsHistoryIterator;
 
@@ -33,20 +34,22 @@
 public class PowerStatsAggregator {
     private static final long UNINITIALIZED = -1;
     private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
-    private final BatteryStatsHistory mHistory;
     private final SparseBooleanArray mEnabledComponents =
             new SparseBooleanArray(BatteryConsumer.POWER_COMPONENT_COUNT + 10);
     private AggregatedPowerStats mStats;
     private int mCurrentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
     private int mCurrentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
 
-    public PowerStatsAggregator(@NonNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig,
-            @NonNull BatteryStatsHistory history) {
-        mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
-        mHistory = history;
+    @VisibleForTesting
+    public PowerStatsAggregator() {
+        this(new AggregatedPowerStatsConfig());
     }
 
-    public AggregatedPowerStatsConfig getConfig() {
+    PowerStatsAggregator(@NonNull AggregatedPowerStatsConfig aggregatedPowerStatsConfig) {
+        mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig;
+    }
+
+    AggregatedPowerStatsConfig getConfig() {
         return mAggregatedPowerStatsConfig;
     }
 
@@ -71,7 +74,7 @@
      * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume
      * the stats in the <code>accept</code> method and never cache it.
      */
-    public void aggregatePowerStats(long startTimeMs, long endTimeMs,
+    public void aggregatePowerStats(BatteryStatsHistory history, long startTimeMs, long endTimeMs,
             Consumer<AggregatedPowerStats> consumer) {
         synchronized (this) {
             if (mStats == null) {
@@ -85,7 +88,7 @@
             long lastTime = 0;
             int lastStates = 0xFFFFFFFF;
             int lastStates2 = 0xFFFFFFFF;
-            try (BatteryStatsHistoryIterator iterator = mHistory.iterate(startTimeMs, endTimeMs)) {
+            try (BatteryStatsHistoryIterator iterator = history.iterate(startTimeMs, endTimeMs)) {
                 while (iterator.hasNext()) {
                     BatteryStats.HistoryItem item = iterator.next();
 
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
similarity index 94%
rename from services/core/java/com/android/server/power/stats/PowerStatsExporter.java
rename to services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
index c5bed24..fab87d6 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.annotation.Nullable;
 import android.os.AggregateBatteryConsumer;
@@ -24,7 +24,11 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.PowerStatsSpan;
+import com.android.server.power.stats.PowerStatsStore;
+import com.android.server.power.stats.format.PowerStatsLayout;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -35,19 +39,18 @@
  * Given a time range, converts accumulated PowerStats to BatteryUsageStats.  Combines
  * stores spans of PowerStats with the yet-unprocessed tail of battery history.
  */
-public class PowerStatsExporter {
+class PowerStatsExporter {
     private static final String TAG = "PowerStatsExporter";
     private final PowerStatsStore mPowerStatsStore;
     private final PowerStatsAggregator mPowerStatsAggregator;
     private final long mBatterySessionTimeSpanSlackMillis;
     private static final long BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS = TimeUnit.MINUTES.toMillis(2);
 
-    public PowerStatsExporter(PowerStatsStore powerStatsStore,
-            PowerStatsAggregator powerStatsAggregator) {
+    PowerStatsExporter(PowerStatsStore powerStatsStore, PowerStatsAggregator powerStatsAggregator) {
         this(powerStatsStore, powerStatsAggregator, BATTERY_SESSION_TIME_SPAN_SLACK_MILLIS);
     }
 
-    public PowerStatsExporter(PowerStatsStore powerStatsStore,
+    PowerStatsExporter(PowerStatsStore powerStatsStore,
             PowerStatsAggregator powerStatsAggregator,
             long batterySessionTimeSpanSlackMillis) {
         mPowerStatsStore = powerStatsStore;
@@ -59,8 +62,8 @@
      * Populates the provided BatteryUsageStats.Builder with power estimates from the accumulated
      * PowerStats, both stored in PowerStatsStore and not-yet processed.
      */
-    public void exportAggregatedPowerStats(BatteryUsageStats.Builder batteryUsageStatsBuilder,
-            long monotonicStartTime, long monotonicEndTime) {
+    void exportAggregatedPowerStats(BatteryUsageStats.Builder batteryUsageStatsBuilder,
+            BatteryStatsHistory history, long monotonicStartTime, long monotonicEndTime) {
         synchronized (mPowerStatsAggregator) {
             boolean hasStoredSpans = false;
             long maxEndTime = monotonicStartTime;
@@ -111,7 +114,7 @@
 
             if (!hasStoredSpans
                     || maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) {
-                mPowerStatsAggregator.aggregatePowerStats(maxEndTime, monotonicEndTime,
+                mPowerStatsAggregator.aggregatePowerStats(history, maxEndTime, monotonicEndTime,
                         stats -> populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats));
             }
             mPowerStatsAggregator.reset();
@@ -140,9 +143,7 @@
             return;
         }
 
-        PowerStatsLayout layout = new PowerStatsLayout();
-        layout.fromExtras(descriptor.extras);
-
+        PowerStatsLayout layout = new PowerStatsLayout(descriptor);
         long[] deviceStats = new long[descriptor.statsArrayLength];
         for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) {
             if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) {
@@ -328,8 +329,8 @@
         BatteryConsumer.Key key = getKeyForPartialTotal(batteryUsageStatsBuilder, allAppsScope,
                 powerComponentId, screenState, powerState);
         if (key != null) {
-                allAppsScope.addConsumedPower(key, powerAllApps,
-                        BatteryConsumer.POWER_MODEL_UNDEFINED);
+            allAppsScope.addConsumedPower(key, powerAllApps,
+                    BatteryConsumer.POWER_MODEL_UNDEFINED);
         }
         allAppsScope.addConsumedPower(powerComponentId, powerAllApps,
                 BatteryConsumer.POWER_MODEL_UNDEFINED);
@@ -393,7 +394,7 @@
         return true;
     }
 
-    void setPowerComponentEnabled(int powerComponentId, boolean enabled) {
+    public void setPowerComponentEnabled(int powerComponentId, boolean enabled) {
         mPowerStatsAggregator.setPowerComponentEnabled(powerComponentId, enabled);
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsProcessor.java
similarity index 97%
rename from services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/PowerStatsProcessor.java
index 6a8c6b12..838fc62 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsProcessor.java
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
-import static com.android.server.power.stats.MultiStateStats.STATE_DOES_NOT_EXIST;
-import static com.android.server.power.stats.MultiStateStats.States.findTrackedStateByName;
+import static com.android.server.power.stats.processor.MultiStateStats.STATE_DOES_NOT_EXIST;
+import static com.android.server.power.stats.processor.MultiStateStats.States.findTrackedStateByName;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -43,7 +43,7 @@
  * 2. For each UID, compute the proportion of the combined estimates in each state
  * and attribute the corresponding portion of the total power estimate in that state to the UID.
  */
-public abstract class PowerStatsProcessor {
+abstract class PowerStatsProcessor {
     private static final String TAG = "PowerStatsProcessor";
 
     private static final double MILLIAMPHOUR_PER_MICROCOULOMB = 1.0 / 1000.0 / 60.0 / 60.0;
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
similarity index 93%
rename from services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
index 8fb1fd6..b295e30 100644
--- a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/ScreenPowerStatsProcessor.java
@@ -14,28 +14,30 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
 
 import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
 import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
 import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import android.os.BatteryStats;
 import android.util.Slog;
 
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.UsageBasedPowerEstimator;
+import com.android.server.power.stats.format.ScreenPowerStatsLayout;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class ScreenPowerStatsProcessor extends PowerStatsProcessor {
+class ScreenPowerStatsProcessor extends PowerStatsProcessor {
     private static final String TAG = "ScreenPowerStatsProcessor";
     private final int mDisplayCount;
     private final UsageBasedPowerEstimator[] mScreenOnPowerEstimators;
@@ -51,7 +53,7 @@
         public double power;
     }
 
-    public ScreenPowerStatsProcessor(PowerProfile powerProfile) {
+    ScreenPowerStatsProcessor(PowerProfile powerProfile) {
         mDisplayCount = powerProfile.getNumDisplays();
         mScreenOnPowerEstimators = new UsageBasedPowerEstimator[mDisplayCount];
         mScreenDozePowerEstimators = new UsageBasedPowerEstimator[mDisplayCount];
diff --git a/services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
similarity index 90%
rename from services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
index 79d8076..67013ea 100644
--- a/services/core/java/com/android/server/power/stats/SensorPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/SensorPowerStatsProcessor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
@@ -25,14 +25,16 @@
 import android.util.SparseArray;
 
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.PowerStatsLayout;
+import com.android.server.power.stats.format.SensorPowerStatsLayout;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Comparator;
 import java.util.List;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
-public class SensorPowerStatsProcessor extends PowerStatsProcessor {
+class SensorPowerStatsProcessor extends PowerStatsProcessor {
     private static final String TAG = "SensorPowerStatsProcessor";
     private static final String ANDROID_SENSOR_TYPE_PREFIX = "android.sensor.";
 
@@ -64,7 +66,7 @@
     private long[] mTmpDeviceStatsArray;
     private long[] mTmpUidStatsArray;
 
-    public SensorPowerStatsProcessor(Supplier<SensorManager> sensorManagerSupplier) {
+    SensorPowerStatsProcessor(Supplier<SensorManager> sensorManagerSupplier) {
         mSensorManagerSupplier = sensorManagerSupplier;
     }
 
@@ -78,16 +80,9 @@
             return false;
         }
 
-        mStatsLayout = new SensorPowerStatsLayout();
-        List<Sensor> sensorList = new ArrayList<>(mSensorManager.getSensorList(Sensor.TYPE_ALL));
-        sensorList.sort(Comparator.comparingInt(Sensor::getId));
-        for (int i = 0; i < sensorList.size(); i++) {
-            Sensor sensor = sensorList.get(i);
-            String label = makeLabel(sensor, sensorList);
-            mStatsLayout.addUidSensorSection(sensor.getHandle(), label);
-        }
-        mStatsLayout.addUidSectionPowerEstimate();
-        mStatsLayout.addDeviceSectionPowerEstimate();
+        List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+        mStatsLayout = new SensorPowerStatsLayout(sensorList.stream().collect(
+                Collectors.toMap(Sensor::getHandle, sensor -> makeLabel(sensor, sensorList))));
 
         PersistableBundle extras = new PersistableBundle();
         mStatsLayout.toExtras(extras);
@@ -231,7 +226,8 @@
         sensorState.startTime = time;
     }
 
-    private void flushPowerStats(PowerComponentAggregatedPowerStats stats, long timestamp) {
+    private void flushPowerStats(
+            PowerComponentAggregatedPowerStats stats, long timestamp) {
         mPowerStats.durationMs = timestamp - mLastUpdateTimestamp;
         stats.addProcessedPowerStats(mPowerStats, timestamp);
 
@@ -240,7 +236,8 @@
         mLastUpdateTimestamp = timestamp;
     }
 
-    private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats,
+    private void computeUidPowerEstimates(
+            PowerComponentAggregatedPowerStats stats,
             List<Integer> uids) {
         List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
         int[] uidSensorDurationPositions = new int[sensorList.size()];
@@ -292,7 +289,8 @@
         }
     }
 
-    private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
+    private void computeDevicePowerEstimates(
+            PowerComponentAggregatedPowerStats stats) {
         for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
             CombinedDeviceStateEstimate estimation =
                     mPlan.combinedDeviceStateEstimations.get(i);
diff --git a/services/core/java/com/android/server/power/stats/VideoPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/VideoPowerStatsProcessor.java
similarity index 80%
rename from services/core/java/com/android/server/power/stats/VideoPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/VideoPowerStatsProcessor.java
index 48dac8a..a6c3807 100644
--- a/services/core/java/com/android/server/power/stats/VideoPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/VideoPowerStatsProcessor.java
@@ -14,16 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 
 import com.android.internal.os.PowerProfile;
+import com.android.server.power.stats.PowerStatsUidResolver;
 
-public class VideoPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
-    public VideoPowerStatsProcessor(PowerProfile powerProfile,
-            PowerStatsUidResolver uidResolver) {
+class VideoPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
+    VideoPowerStatsProcessor(PowerProfile powerProfile, PowerStatsUidResolver uidResolver) {
         super(BatteryConsumer.POWER_COMPONENT_VIDEO, uidResolver,
                 powerProfile.getAveragePower(PowerProfile.POWER_VIDEO));
     }
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java
similarity index 98%
rename from services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
rename to services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java
index 4e035c3..0df01cf 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/processor/WifiPowerStatsProcessor.java
@@ -14,18 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import android.util.Slog;
 
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.UsageBasedPowerEstimator;
+import com.android.server.power.stats.format.WifiPowerStatsLayout;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-public class WifiPowerStatsProcessor extends PowerStatsProcessor {
+class WifiPowerStatsProcessor extends PowerStatsProcessor {
     private static final String TAG = "WifiPowerStatsProcessor";
     private static final boolean DEBUG = false;
 
@@ -46,7 +48,7 @@
     private long[] mTmpUidStatsArray;
     private boolean mHasWifiPowerController;
 
-    public WifiPowerStatsProcessor(PowerProfile powerProfile) {
+    WifiPowerStatsProcessor(PowerProfile powerProfile) {
         mRxPowerEstimator = new UsageBasedPowerEstimator(
                 powerProfile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX));
         mTxPowerEstimator = new UsageBasedPowerEstimator(
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 1c786e6..68026ea 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.Flags.provideInfoOfApkInApex;
 
+import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
+
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -40,6 +42,7 @@
 import android.os.SystemProperties;
 import android.sysprop.CrashRecoveryProperties;
 import android.util.ArraySet;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -532,11 +535,13 @@
     private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
             @FailureReasons int rollbackReason) {
         assertInWorkerThread();
+        String failedPackageName = (failedPackage == null ? null : failedPackage.getPackageName());
 
         Slog.i(TAG, "Rolling back package. RollbackId: " + rollback.getRollbackId()
-                + " failedPackage: "
-                + (failedPackage == null ? null : failedPackage.getPackageName())
+                + " failedPackage: " + failedPackageName
                 + " rollbackReason: " + rollbackReason);
+        logCrashRecoveryEvent(Log.DEBUG, String.format("Rolling back %s. Reason: %s",
+                failedPackageName, rollbackReason));
         final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
         int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
         final String failedPackageToLog;
@@ -724,6 +729,7 @@
         }
 
         Slog.i(TAG, "Rolling back all available low impact rollbacks");
+        logCrashRecoveryEvent(Log.DEBUG, "Rolling back all available. Reason: " + rollbackReason);
         // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
         // pending staged rollbacks are handled.
         for (RollbackInfo rollback : lowImpactRollbacks) {
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index ac56043..b35a0a7 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -71,6 +71,7 @@
 import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs;
 import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
 import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats;
+import static com.android.server.stats.pull.netstats.NetworkStatsUtils.isAddEntriesSupported;
 
 import static libcore.io.IoUtils.closeQuietly;
 
@@ -1388,14 +1389,22 @@
 
     @NonNull
     private static NetworkStats removeEmptyEntries(NetworkStats stats) {
-        NetworkStats ret = new NetworkStats(0, 1);
+        final ArrayList<NetworkStats.Entry> entries = new ArrayList<>();
         for (NetworkStats.Entry e : stats) {
             if (e.getRxBytes() != 0 || e.getRxPackets() != 0 || e.getTxBytes() != 0
                     || e.getTxPackets() != 0 || e.getOperations() != 0) {
-                ret = ret.addEntry(e);
+                entries.add(e);
             }
         }
-        return ret;
+        if (isAddEntriesSupported()) {
+            return new NetworkStats(0, entries.size()).addEntries(entries);
+        } else {
+            NetworkStats outputStats = new NetworkStats(0L, 1);
+            for (NetworkStats.Entry e : entries) {
+                outputStats = outputStats.addEntry(e);
+            }
+            return outputStats;
+        }
     }
 
     private void addNetworkStats(int atomTag, @NonNull List<StatsEvent> ret,
@@ -1720,11 +1729,19 @@
     @NonNull
     private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
             @NonNull Function<NetworkStats.Entry, NetworkStats.Entry> slicer) {
-        NetworkStats ret = new NetworkStats(0, 1);
+        final ArrayList<NetworkStats.Entry> entries = new ArrayList();
         for (NetworkStats.Entry e : stats) {
-            ret = ret.addEntry(slicer.apply(e));
+            entries.add(slicer.apply(e));
         }
-        return ret;
+        if (isAddEntriesSupported()) {
+            return new NetworkStats(0, entries.size()).addEntries(entries);
+        } else {
+            NetworkStats outputStats = new NetworkStats(0L, 1);
+            for (NetworkStats.Entry e : entries) {
+                outputStats = outputStats.addEntry(e);
+            }
+            return outputStats;
+        }
     }
 
     private void registerWifiBytesTransferBackground() {
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
index de58852..0318bdd 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java
@@ -24,6 +24,9 @@
 import android.app.usage.NetworkStats;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.stats.Flags;
+
+import java.util.ArrayList;
 
 /**
  * Utility methods for accessing {@link android.net.NetworkStats}.
@@ -35,12 +38,21 @@
      */
     public static android.net.NetworkStats fromPublicNetworkStats(
             NetworkStats publiceNetworkStats) {
-        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+        final ArrayList<android.net.NetworkStats.Entry> entries = new ArrayList<>();
         while (publiceNetworkStats.hasNextBucket()) {
             NetworkStats.Bucket bucket = new NetworkStats.Bucket();
             publiceNetworkStats.getNextBucket(bucket);
-            final android.net.NetworkStats.Entry entry = fromBucket(bucket);
-            stats = stats.addEntry(entry);
+            entries.add(fromBucket(bucket));
+        }
+        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 1);
+        // The new API is only supported on devices running the mainline version of `NetworkStats`.
+        // It should always be used when available for memory efficiency.
+        if (isAddEntriesSupported()) {
+            stats = stats.addEntries(entries);
+        } else {
+            for (android.net.NetworkStats.Entry entry : entries) {
+                stats = stats.addEntry(entry);
+            }
         }
         return stats;
     }
@@ -106,4 +118,8 @@
         }
         return 0;
     }
+
+    public static boolean isAddEntriesSupported() {
+        return Flags.netstatsUseAddEntries();
+    }
 }
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index f360837..afea303 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -1,6 +1,20 @@
 package: "com.android.server.stats"
 container: "system"
 
+# Note: To ensure compatibility across all release configurations, initiate the ramp-up process
+# only after the 'com.android.net.flags.netstats_add_entries' flag has been fully deployed.
+# This flag provides the necessary API from the Connectivity module.
+# The flag was added because the flag 'com.android.net.flags.netstats_add_entries' for API
+# is already being rolled out, and modifying behavior during an active rollout might
+# lead to unwanted issues.
+flag {
+    name: "netstats_use_add_entries"
+    namespace: "statsd"
+    description: "Use NetworkStats#addEntries to reduce memory footprint"
+    bug: "335680025"
+    is_fixed_read_only: true
+}
+
 flag {
     name: "add_mobile_bytes_transfer_by_proc_state_puller"
     namespace: "statsd"
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 2faa68a..09d2a02 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -168,11 +168,6 @@
      */
     void onDisplayReady(int displayId);
 
-    /**
-     * Notifies System UI whether the recents animation is running.
-     */
-    void onRecentsAnimationStateChanged(boolean running);
-
     /** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
     void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7d812ee..0fd5967 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -699,17 +699,6 @@
         }
 
         @Override
-        public void onRecentsAnimationStateChanged(boolean running) {
-            IStatusBar bar = mBar;
-            if (bar != null) {
-                try {
-                    bar.onRecentsAnimationStateChanged(running);
-                } catch (RemoteException ex) {}
-            }
-
-        }
-
-        @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
                 @Behavior int behavior, @InsetsType int requestedVisibleTypes,
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/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 480db25..8e24e9f 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -38,6 +38,7 @@
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.service.tracing.TraceReportService;
 import android.tracing.ITracingServiceProxy;
 import android.tracing.TraceReportParams;
@@ -87,6 +88,8 @@
             TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING;
     private static final int REPORT_SVC_COMM_ERROR =
             TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR;
+    private static final String NOTIFY_SESSION_ENDED_SETTING = "should_notify_trace_session_ended";
+    private static final int ENABLED = 1;
 
     private final Context mContext;
     private final PackageManager mPackageManager;
@@ -97,10 +100,22 @@
         /**
          * Notifies system tracing app that a tracing session has ended. sessionStolen is ignored,
          * as trace sessions are no longer stolen and are always cloned instead.
+         * <p>
+         * Cases exist where user-flows besides Traceur's QS Tile may end long-trace sessions. In
+         * these cases, a Global int will be set to flag the upcoming notifyTraceSessionEnded call
+         * as purposely muted once.
          */
         @Override
         public void notifyTraceSessionEnded(boolean sessionStolen /* unused */) {
-            TracingServiceProxy.this.notifyTraceur();
+            long identity = Binder.clearCallingIdentity();
+            if (Settings.Global.getInt(mContext.getContentResolver(),
+                    NOTIFY_SESSION_ENDED_SETTING, ENABLED) == ENABLED) {
+                TracingServiceProxy.this.notifyTraceur();
+            } else {
+                Settings.Global.putInt(mContext.getContentResolver(), NOTIFY_SESSION_ENDED_SETTING,
+                        ENABLED);
+            }
+            Binder.restoreCallingIdentity(identity);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index cda86fa..60b5e65 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -19,6 +19,7 @@
 import static android.media.AudioManager.DEVICE_NONE;
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
+import static android.media.tv.flags.Flags.tifUnbindInactiveTis;
 import static android.media.tv.flags.Flags.kidsModeTvdbSharing;
 
 import android.annotation.NonNull;
@@ -250,7 +251,7 @@
     }
 
     private void registerBroadcastReceivers() {
-        PackageMonitor monitor = new PackageMonitor() {
+        PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) {
             private void buildTvInputList(String[] packages) {
                 int userId = getChangingUserId();
                 synchronized (mLock) {
@@ -3305,7 +3306,20 @@
         return filteredDisplayName;
     }
 
-    private static final class UserState {
+    private class TvInputManagerCallbackList extends RemoteCallbackList<ITvInputManagerCallback> {
+        @Override
+        public void onCallbackDied(ITvInputManagerCallback callback) {
+            synchronized (mLock) {
+                for (int i = 0; i < mUserStates.size(); i++) {
+                    int userId = mUserStates.keyAt(i);
+                    UserState userState = getOrCreateUserStateLocked(userId);
+                    userState.callbackPidUidMap.remove(callback);
+                }
+            }
+        }
+    }
+
+    private final class UserState {
         // A mapping from the TV input id to its TvInputState.
         private Map<String, TvInputState> inputMap = new HashMap<>();
 
@@ -3326,8 +3340,8 @@
         private final Map<IBinder, SessionState> sessionStateMap = new HashMap<>();
 
         // A list of callbacks.
-        private final RemoteCallbackList<ITvInputManagerCallback> mCallbacks =
-                new RemoteCallbackList<>();
+        private final TvInputManagerCallbackList mCallbacks =
+                new TvInputManagerCallbackList();
 
         private final Map<ITvInputManagerCallback, Pair<Integer, Integer>> callbackPidUidMap =
                 new HashMap<>();
@@ -4515,12 +4529,14 @@
                     break;
                 }
                 case MSG_UPDATE_HARDWARE_TIS_BINDING:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    int userId = (int) args.arg1;
-                    synchronized (mLock) {
-                        updateHardwareTvInputServiceBindingLocked(userId);
+                    if (tifUnbindInactiveTis()) {
+                        SomeArgs args = (SomeArgs) msg.obj;
+                        int userId = (int) args.arg1;
+                        synchronized (mLock) {
+                            updateHardwareTvInputServiceBindingLocked(userId);
+                        }
+                        args.recycle();
                     }
-                    args.recycle();
                     break;
                 default: {
                     Slog.w(TAG, "unhandled message code: " + msg.what);
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index edd2fa9..6a7fc6d 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -519,7 +519,7 @@
     }
 
     private void registerBroadcastReceivers() {
-        PackageMonitor monitor = new PackageMonitor() {
+        PackageMonitor monitor = new PackageMonitor(/* supportsPackageRestartQuery */ true) {
             private void buildTvInteractiveAppServiceList(String[] packages) {
                 int userId = getChangingUserId();
                 synchronized (mLock) {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
index 440d251..eb5361c 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -26,6 +26,11 @@
  * @hide
  */
 public class CasResource {
+    /**
+     * Handle of the current resource. Should not be changed and should be aligned with the driver
+     * level implementation.
+     */
+    final int mHandle;
 
     private final int mSystemId;
 
@@ -39,11 +44,16 @@
     private Map<Integer, Integer> mOwnerClientIdsToSessionNum = new HashMap<>();
 
     CasResource(Builder builder) {
+        this.mHandle = builder.mHandle;
         this.mSystemId = builder.mSystemId;
         this.mMaxSessionNum = builder.mMaxSessionNum;
         this.mAvailableSessionNum = builder.mMaxSessionNum;
     }
 
+    public int getHandle() {
+        return mHandle;
+    }
+
     public int getSystemId() {
         return mSystemId;
     }
@@ -136,10 +146,12 @@
      */
     public static class Builder {
 
+        private final int mHandle;
         private int mSystemId;
         protected int mMaxSessionNum;
 
-        Builder(int systemId) {
+        Builder(int handle, int systemId) {
+            this.mHandle = handle;
             this.mSystemId = systemId;
         }
 
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java
index 31149f3..5cef729 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java
@@ -42,8 +42,8 @@
      * Builder class for {@link CiCamResource}.
      */
     public static class Builder extends CasResource.Builder {
-        Builder(int systemId) {
-            super(systemId);
+        Builder(int handle, int systemId) {
+            super(handle, systemId);
         }
 
         /**
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 0afb049..9229f7f 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -203,13 +203,7 @@
         @Override
         public void unregisterClientProfile(int clientId) throws RemoteException {
             enforceTrmAccessPermission("unregisterClientProfile");
-            synchronized (mLock) {
-                if (!checkClientExists(clientId)) {
-                    Slog.e(TAG, "Unregistering non exists client:" + clientId);
-                    return;
-                }
-                unregisterClientProfileInternal(clientId);
-            }
+            unregisterClientProfileInternal(clientId);
         }
 
         @Override
@@ -291,20 +285,7 @@
                 Slog.e(TAG, "frontendHandle can't be null");
                 return false;
             }
-            synchronized (mLock) {
-                if (!checkClientExists(request.clientId)) {
-                    Slog.e(TAG, "Request frontend from unregistered client: "
-                            + request.clientId);
-                    return false;
-                }
-                // If the request client is holding or sharing a frontend, throw an exception.
-                if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
-                    Slog.e(TAG, "Release frontend before requesting another one. Client id: "
-                            + request.clientId);
-                    return false;
-                }
-                return requestFrontendInternal(request, frontendHandle);
-            }
+            return requestFrontendInternal(request, frontendHandle);
         }
 
         @Override
@@ -376,13 +357,7 @@
             if (demuxHandle == null) {
                 throw new RemoteException("demuxHandle can't be null");
             }
-            synchronized (mLock) {
-                if (!checkClientExists(request.clientId)) {
-                    throw new RemoteException("Request demux from unregistered client:"
-                            + request.clientId);
-                }
-                return requestDemuxInternal(request, demuxHandle);
-            }
+            return requestDemuxInternal(request, demuxHandle);
         }
 
         @Override
@@ -409,13 +384,7 @@
             if (casSessionHandle == null) {
                 throw new RemoteException("casSessionHandle can't be null");
             }
-            synchronized (mLock) {
-                if (!checkClientExists(request.clientId)) {
-                    throw new RemoteException("Request cas from unregistered client:"
-                            + request.clientId);
-                }
-                return requestCasSessionInternal(request, casSessionHandle);
-            }
+            return requestCasSessionInternal(request, casSessionHandle);
         }
 
         @Override
@@ -425,13 +394,7 @@
             if (ciCamHandle == null) {
                 throw new RemoteException("ciCamHandle can't be null");
             }
-            synchronized (mLock) {
-                if (!checkClientExists(request.clientId)) {
-                    throw new RemoteException("Request ciCam from unregistered client:"
-                            + request.clientId);
-                }
-                return requestCiCamInternal(request, ciCamHandle);
-            }
+            return requestCiCamInternal(request, ciCamHandle);
         }
 
         @Override
@@ -442,42 +405,14 @@
             if (lnbHandle == null) {
                 throw new RemoteException("lnbHandle can't be null");
             }
-            synchronized (mLock) {
-                if (!checkClientExists(request.clientId)) {
-                    throw new RemoteException("Request lnb from unregistered client:"
-                            + request.clientId);
-                }
-                return requestLnbInternal(request, lnbHandle);
-            }
+            return requestLnbInternal(request, lnbHandle);
         }
 
         @Override
         public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException {
             enforceTunerAccessPermission("releaseFrontend");
             enforceTrmAccessPermission("releaseFrontend");
-            if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
-                    frontendHandle)) {
-                throw new RemoteException("frontendHandle can't be invalid");
-            }
-            synchronized (mLock) {
-                if (!checkClientExists(clientId)) {
-                    throw new RemoteException("Release frontend from unregistered client:"
-                            + clientId);
-                }
-                FrontendResource fe = getFrontendResource(frontendHandle);
-                if (fe == null) {
-                    throw new RemoteException("Releasing frontend does not exist.");
-                }
-                int ownerClientId = fe.getOwnerClientId();
-                ClientProfile ownerProfile = getClientProfile(ownerClientId);
-                if (ownerClientId != clientId
-                        && (ownerProfile != null
-                              && !ownerProfile.getShareFeClientIds().contains(clientId))) {
-                    throw new RemoteException(
-                            "Client is not the current owner of the releasing fe.");
-                }
-                releaseFrontendInternal(fe, clientId);
-            }
+            releaseFrontendInternal(frontendHandle, clientId);
         }
 
         @Override
@@ -746,17 +681,23 @@
 
     @VisibleForTesting
     protected void unregisterClientProfileInternal(int clientId) {
-        if (DEBUG) {
-            Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
-        }
-        removeClientProfile(clientId);
-        // Remove the Media Resource Manager callingPid to tvAppId mapping
-        if (mMediaResourceManager != null) {
-            try {
-                mMediaResourceManager.overridePid(Binder.getCallingPid(), -1);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister,"
-                        + " remote exception: " + e);
+        synchronized (mLock) {
+            if (!checkClientExists(clientId)) {
+                Slog.e(TAG, "Unregistering non exists client:" + clientId);
+                return;
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")");
+            }
+            removeClientProfile(clientId);
+            // Remove the Media Resource Manager callingPid to tvAppId mapping
+            if (mMediaResourceManager != null) {
+                try {
+                    mMediaResourceManager.overridePid(Binder.getCallingPid(), -1);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister,"
+                            + " remote exception: " + e);
+                }
             }
         }
     }
@@ -992,10 +933,14 @@
             return;
         }
         // Add the new Cas Resource.
-        cas = new CasResource.Builder(casSystemId)
+        int casSessionHandle = generateResourceHandle(
+                TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSystemId);
+        cas = new CasResource.Builder(casSessionHandle, casSystemId)
                              .maxSessionNum(maxSessionNum)
                              .build();
-        ciCam = new CiCamResource.Builder(casSystemId)
+        int ciCamHandle = generateResourceHandle(
+                TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, casSystemId);
+        ciCam = new CiCamResource.Builder(ciCamHandle, casSystemId)
                              .maxSessionNum(maxSessionNum)
                              .build();
         addCasResource(cas);
@@ -1007,86 +952,120 @@
         if (DEBUG) {
             Slog.d(TAG, "requestFrontend(request=" + request + ")");
         }
-
-        frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        ClientProfile requestClient = getClientProfile(request.clientId);
-        // TODO: check if this is really needed
-        if (requestClient == null) {
+        int[] reclaimOwnerId = new int[1];
+        if (!claimFrontend(request, frontendHandle, reclaimOwnerId)) {
             return false;
         }
-        clientPriorityUpdateOnRequest(requestClient);
-        int grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        // Priority max value is 1000
-        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
-        boolean isRequestFromSameProcess = false;
-        // If the desired frontend id was specified, we only need to check the frontend.
-        boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID;
-        for (FrontendResource fr : getFrontendResources().values()) {
-            int frontendId = getResourceIdFromHandle(fr.getHandle());
-            if (fr.getType() == request.frontendType
-                    && (!hasDesiredFrontend || frontendId == request.desiredId)) {
-                if (!fr.isInUse()) {
-                    // Unused resource cannot be acquired if the max is already reached, but
-                    // TRM still has to look for the reclaim candidate
-                    if (isFrontendMaxNumUseReached(request.frontendType)) {
-                        continue;
-                    }
-                    // Grant unused frontend with no exclusive group members first.
-                    if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) {
-                        grantingFrontendHandle = fr.getHandle();
-                        break;
-                    } else if (grantingFrontendHandle
-                            == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
-                        // Grant the unused frontend with lower id first if all the unused
-                        // frontends have exclusive group members.
-                        grantingFrontendHandle = fr.getHandle();
-                    }
-                } else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
-                    // Record the frontend id with the lowest client priority among all the
-                    // in use frontends when no available frontend has been found.
-                    int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
-                    if (currentLowestPriority > priority) {
-                        // we need to check the max used num if the target frontend type is not
-                        // currently in primary use (and simply blocked due to exclusive group)
-                        ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId());
-                        int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
-                        FrontendResource primaryFe = getFrontendResource(primaryFeId);
-                        if (fr.getType() != primaryFe.getType()
-                                && isFrontendMaxNumUseReached(fr.getType())) {
+        if (frontendHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+            return false;
+        }
+        if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+            if (!reclaimResource(reclaimOwnerId[0], TunerResourceManager
+                    .TUNER_RESOURCE_TYPE_FRONTEND)) {
+                return false;
+            }
+            synchronized (mLock) {
+                if (getFrontendResource(frontendHandle[0]).isInUse()) {
+                    Slog.e(TAG, "Reclaimed frontend still in use");
+                    return false;
+                }
+                updateFrontendClientMappingOnNewGrant(frontendHandle[0], request.clientId);
+            }
+        }
+        return true;
+    }
+
+    protected boolean claimFrontend(
+            TunerFrontendRequest request,
+            int[] frontendHandle,
+            int[] reclaimOwnerId
+    ) {
+        frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        reclaimOwnerId[0] = INVALID_CLIENT_ID;
+        synchronized (mLock) {
+            if (!checkClientExists(request.clientId)) {
+                Slog.e(TAG, "Request frontend from unregistered client: "
+                        + request.clientId);
+                return false;
+            }
+            // If the request client is holding or sharing a frontend, throw an exception.
+            if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
+                Slog.e(TAG, "Release frontend before requesting another one. Client id: "
+                        + request.clientId);
+                return false;
+            }
+            ClientProfile requestClient = getClientProfile(request.clientId);
+            clientPriorityUpdateOnRequest(requestClient);
+            FrontendResource grantingFrontend = null;
+            FrontendResource inUseLowestPriorityFrontend = null;
+            // Priority max value is 1000
+            int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+            boolean isRequestFromSameProcess = false;
+            // If the desired frontend id was specified, we only need to check the frontend.
+            boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest
+                    .DEFAULT_DESIRED_ID;
+            for (FrontendResource fr : getFrontendResources().values()) {
+                int frontendId = getResourceIdFromHandle(fr.getHandle());
+                if (fr.getType() == request.frontendType
+                        && (!hasDesiredFrontend || frontendId == request.desiredId)) {
+                    if (!fr.isInUse()) {
+                        // Unused resource cannot be acquired if the max is already reached, but
+                        // TRM still has to look for the reclaim candidate
+                        if (isFrontendMaxNumUseReached(request.frontendType)) {
                             continue;
                         }
-                        // update the target frontend
-                        inUseLowestPriorityFrHandle = fr.getHandle();
-                        currentLowestPriority = priority;
-                        isRequestFromSameProcess = (requestClient.getProcessId()
-                            == (getClientProfile(fr.getOwnerClientId())).getProcessId());
+                        // Grant unused frontend with no exclusive group members first.
+                        if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) {
+                            grantingFrontend = fr;
+                            break;
+                        } else if (grantingFrontend == null) {
+                            // Grant the unused frontend with lower id first if all the unused
+                            // frontends have exclusive group members.
+                            grantingFrontend = fr;
+                        }
+                    } else if (grantingFrontend == null) {
+                        // Record the frontend id with the lowest client priority among all the
+                        // in use frontends when no available frontend has been found.
+                        int priority = getFrontendHighestClientPriority(fr.getOwnerClientId());
+                        if (currentLowestPriority > priority) {
+                            // we need to check the max used num if the target frontend type is not
+                            // currently in primary use (and simply blocked due to exclusive group)
+                            ClientProfile targetOwnerProfile =
+                                    getClientProfile(fr.getOwnerClientId());
+                            int primaryFeId = targetOwnerProfile.getPrimaryFrontend();
+                            FrontendResource primaryFe = getFrontendResource(primaryFeId);
+                            if (fr.getType() != primaryFe.getType()
+                                    && isFrontendMaxNumUseReached(fr.getType())) {
+                                continue;
+                            }
+                            // update the target frontend
+                            inUseLowestPriorityFrontend = fr;
+                            currentLowestPriority = priority;
+                            isRequestFromSameProcess = (requestClient.getProcessId()
+                                == (getClientProfile(fr.getOwnerClientId())).getProcessId());
+                        }
                     }
                 }
             }
-        }
 
-        // Grant frontend when there is unused resource.
-        if (grantingFrontendHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
-            frontendHandle[0] = grantingFrontendHandle;
-            updateFrontendClientMappingOnNewGrant(grantingFrontendHandle, request.clientId);
-            return true;
-        }
-
-        // When all the resources are occupied, grant the lowest priority resource if the
-        // request client has higher priority.
-        if (inUseLowestPriorityFrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
-            && ((requestClient.getPriority() > currentLowestPriority) || (
-            (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
-            if (!reclaimResource(
-                    getFrontendResource(inUseLowestPriorityFrHandle).getOwnerClientId(),
-                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
-                return false;
+            // Grant frontend when there is unused resource.
+            if (grantingFrontend != null) {
+                updateFrontendClientMappingOnNewGrant(grantingFrontend.getHandle(),
+                        request.clientId);
+                frontendHandle[0] = grantingFrontend.getHandle();
+                return true;
             }
-            frontendHandle[0] = inUseLowestPriorityFrHandle;
-            updateFrontendClientMappingOnNewGrant(
-                    inUseLowestPriorityFrHandle, request.clientId);
-            return true;
+
+            // When all the resources are occupied, grant the lowest priority resource if the
+            // request client has higher priority.
+            if (inUseLowestPriorityFrontend != null
+                    && ((requestClient.getPriority() > currentLowestPriority)
+                    || ((requestClient.getPriority() == currentLowestPriority)
+                    && isRequestFromSameProcess))) {
+                frontendHandle[0] = inUseLowestPriorityFrontend.getHandle();
+                reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId();
+                return true;
+            }
         }
 
         return false;
@@ -1192,165 +1171,257 @@
     }
 
     @VisibleForTesting
-    protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
+    protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle)
+            throws RemoteException {
         if (DEBUG) {
             Slog.d(TAG, "requestLnb(request=" + request + ")");
         }
-
-        lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        ClientProfile requestClient = getClientProfile(request.clientId);
-        clientPriorityUpdateOnRequest(requestClient);
-        int grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        // Priority max value is 1000
-        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
-        boolean isRequestFromSameProcess = false;
-        for (LnbResource lnb : getLnbResources().values()) {
-            if (!lnb.isInUse()) {
-                // Grant the unused lnb with lower handle first
-                grantingLnbHandle = lnb.getHandle();
-                break;
-            } else {
-                // Record the lnb id with the lowest client priority among all the
-                // in use lnb when no available lnb has been found.
-                int priority = updateAndGetOwnerClientPriority(lnb.getOwnerClientId());
-                if (currentLowestPriority > priority) {
-                    inUseLowestPriorityLnbHandle = lnb.getHandle();
-                    currentLowestPriority = priority;
-                    isRequestFromSameProcess = (requestClient.getProcessId()
-                        == (getClientProfile(lnb.getOwnerClientId())).getProcessId());
-                }
-            }
+        int[] reclaimOwnerId = new int[1];
+        if (!claimLnb(request, lnbHandle, reclaimOwnerId)) {
+            return false;
         }
-
-        // Grant Lnb when there is unused resource.
-        if (grantingLnbHandle > -1) {
-            lnbHandle[0] = grantingLnbHandle;
-            updateLnbClientMappingOnNewGrant(grantingLnbHandle, request.clientId);
-            return true;
+        if (lnbHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+            return false;
         }
-
-        // When all the resources are occupied, grant the lowest priority resource if the
-        // request client has higher priority.
-        if (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE
-            && ((requestClient.getPriority() > currentLowestPriority) || (
-            (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
-            if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(),
+        if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+            if (!reclaimResource(reclaimOwnerId[0],
                     TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) {
                 return false;
             }
-            lnbHandle[0] = inUseLowestPriorityLnbHandle;
-            updateLnbClientMappingOnNewGrant(inUseLowestPriorityLnbHandle, request.clientId);
-            return true;
+            synchronized (mLock) {
+                if (getLnbResource(lnbHandle[0]).isInUse()) {
+                    Slog.e(TAG, "Reclaimed lnb still in use");
+                    return false;
+                }
+                updateLnbClientMappingOnNewGrant(lnbHandle[0], request.clientId);
+            }
+        }
+        return true;
+    }
+
+    protected boolean claimLnb(TunerLnbRequest request, int[] lnbHandle, int[] reclaimOwnerId)
+            throws RemoteException {
+        lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        reclaimOwnerId[0] = INVALID_CLIENT_ID;
+        synchronized (mLock) {
+            if (!checkClientExists(request.clientId)) {
+                throw new RemoteException("Request lnb from unregistered client:"
+                        + request.clientId);
+            }
+            ClientProfile requestClient = getClientProfile(request.clientId);
+            clientPriorityUpdateOnRequest(requestClient);
+            LnbResource grantingLnb = null;
+            LnbResource inUseLowestPriorityLnb = null;
+            // Priority max value is 1000
+            int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+            boolean isRequestFromSameProcess = false;
+            for (LnbResource lnb : getLnbResources().values()) {
+                if (!lnb.isInUse()) {
+                    // Grant the unused lnb with lower handle first
+                    grantingLnb = lnb;
+                    break;
+                } else {
+                    // Record the lnb id with the lowest client priority among all the
+                    // in use lnb when no available lnb has been found.
+                    int priority = updateAndGetOwnerClientPriority(lnb.getOwnerClientId());
+                    if (currentLowestPriority > priority) {
+                        inUseLowestPriorityLnb = lnb;
+                        currentLowestPriority = priority;
+                        isRequestFromSameProcess = (requestClient.getProcessId()
+                            == (getClientProfile(lnb.getOwnerClientId())).getProcessId());
+                    }
+                }
+            }
+
+            // Grant Lnb when there is unused resource.
+            if (grantingLnb != null) {
+                updateLnbClientMappingOnNewGrant(grantingLnb.getHandle(), request.clientId);
+                lnbHandle[0] = grantingLnb.getHandle();
+                return true;
+            }
+
+            // When all the resources are occupied, grant the lowest priority resource if the
+            // request client has higher priority.
+            if (inUseLowestPriorityLnb != null
+                    && ((requestClient.getPriority() > currentLowestPriority) || (
+                    (requestClient.getPriority() == currentLowestPriority)
+                        && isRequestFromSameProcess))) {
+                lnbHandle[0] = inUseLowestPriorityLnb.getHandle();
+                reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId();
+                return true;
+            }
         }
 
         return false;
     }
 
     @VisibleForTesting
-    protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) {
+    protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle)
+            throws RemoteException {
         if (DEBUG) {
             Slog.d(TAG, "requestCasSession(request=" + request + ")");
         }
-        CasResource cas = getCasResource(request.casSystemId);
-        // Unregistered Cas System is treated as having unlimited sessions.
-        if (cas == null) {
-            cas = new CasResource.Builder(request.casSystemId)
-                                 .maxSessionNum(Integer.MAX_VALUE)
-                                 .build();
-            addCasResource(cas);
+        int[] reclaimOwnerId = new int[1];
+        if (!claimCasSession(request, casSessionHandle, reclaimOwnerId)) {
+            return false;
         }
-        casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        ClientProfile requestClient = getClientProfile(request.clientId);
-        clientPriorityUpdateOnRequest(requestClient);
-        int lowestPriorityOwnerId = -1;
-        // Priority max value is 1000
-        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
-        boolean isRequestFromSameProcess = false;
-        if (!cas.isFullyUsed()) {
-            casSessionHandle[0] = generateResourceHandle(
-                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
-            updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
-            return true;
+        if (casSessionHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+            return false;
         }
-        for (int ownerId : cas.getOwnerClientIds()) {
-            // Record the client id with lowest priority that is using the current Cas system.
-            int priority = updateAndGetOwnerClientPriority(ownerId);
-            if (currentLowestPriority > priority) {
-                lowestPriorityOwnerId = ownerId;
-                currentLowestPriority = priority;
-                isRequestFromSameProcess = (requestClient.getProcessId()
-                    == (getClientProfile(ownerId)).getProcessId());
-            }
-        }
-
-        // When all the Cas sessions are occupied, reclaim the lowest priority client if the
-        // request client has higher priority.
-        if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
-        || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
-            if (!reclaimResource(lowestPriorityOwnerId,
+        if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+            if (!reclaimResource(reclaimOwnerId[0],
                     TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
                 return false;
             }
-            casSessionHandle[0] = generateResourceHandle(
-                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
-            updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
-            return true;
+            synchronized (mLock) {
+                if (getCasResource(request.casSystemId).isFullyUsed()) {
+                    Slog.e(TAG, "Reclaimed cas still fully used");
+                    return false;
+                }
+                updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
+            }
         }
+        return true;
+    }
+
+    protected boolean claimCasSession(CasSessionRequest request, int[] casSessionHandle,
+            int[] reclaimOwnerId) throws RemoteException {
+        casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        reclaimOwnerId[0] = INVALID_CLIENT_ID;
+        synchronized (mLock) {
+            if (!checkClientExists(request.clientId)) {
+                throw new RemoteException("Request cas from unregistered client:"
+                        + request.clientId);
+            }
+            CasResource cas = getCasResource(request.casSystemId);
+            // Unregistered Cas System is treated as having unlimited sessions.
+            if (cas == null) {
+                int resourceHandle = generateResourceHandle(
+                        TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, request.clientId);
+                cas = new CasResource.Builder(resourceHandle, request.casSystemId)
+                                    .maxSessionNum(Integer.MAX_VALUE)
+                                    .build();
+                addCasResource(cas);
+            }
+            ClientProfile requestClient = getClientProfile(request.clientId);
+            clientPriorityUpdateOnRequest(requestClient);
+            int lowestPriorityOwnerId = INVALID_CLIENT_ID;
+            // Priority max value is 1000
+            int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+            boolean isRequestFromSameProcess = false;
+            if (!cas.isFullyUsed()) {
+                updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId);
+                casSessionHandle[0] = cas.getHandle();
+                return true;
+            }
+            for (int ownerId : cas.getOwnerClientIds()) {
+                // Record the client id with lowest priority that is using the current Cas system.
+                int priority = updateAndGetOwnerClientPriority(ownerId);
+                if (currentLowestPriority > priority) {
+                    lowestPriorityOwnerId = ownerId;
+                    currentLowestPriority = priority;
+                    isRequestFromSameProcess = (requestClient.getProcessId()
+                        == (getClientProfile(ownerId)).getProcessId());
+                }
+            }
+
+            // When all the Cas sessions are occupied, reclaim the lowest priority client if the
+            // request client has higher priority.
+            if (lowestPriorityOwnerId != INVALID_CLIENT_ID
+                    && ((requestClient.getPriority() > currentLowestPriority)
+                    || ((requestClient.getPriority() == currentLowestPriority)
+                    && isRequestFromSameProcess))) {
+                casSessionHandle[0] = cas.getHandle();
+                reclaimOwnerId[0] = lowestPriorityOwnerId;
+                return true;
+            }
+        }
+
         return false;
     }
 
     @VisibleForTesting
-    protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle) {
+    protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle)
+            throws RemoteException {
         if (DEBUG) {
             Slog.d(TAG, "requestCiCamInternal(TunerCiCamRequest=" + request + ")");
         }
-        CiCamResource ciCam = getCiCamResource(request.ciCamId);
-        // Unregistered Cas System is treated as having unlimited sessions.
-        if (ciCam == null) {
-            ciCam = new CiCamResource.Builder(request.ciCamId)
-                                     .maxSessionNum(Integer.MAX_VALUE)
-                                     .build();
-            addCiCamResource(ciCam);
+        int[] reclaimOwnerId = new int[1];
+        if (!claimCiCam(request, ciCamHandle, reclaimOwnerId)) {
+            return false;
         }
-        ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        ClientProfile requestClient = getClientProfile(request.clientId);
-        clientPriorityUpdateOnRequest(requestClient);
-        int lowestPriorityOwnerId = -1;
-        // Priority max value is 1000
-        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
-        boolean isRequestFromSameProcess = false;
-        if (!ciCam.isFullyUsed()) {
-            ciCamHandle[0] = generateResourceHandle(
-                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
-            updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
-            return true;
+        if (ciCamHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+            return false;
         }
-        for (int ownerId : ciCam.getOwnerClientIds()) {
-            // Record the client id with lowest priority that is using the current Cas system.
-            int priority = updateAndGetOwnerClientPriority(ownerId);
-            if (currentLowestPriority > priority) {
-                lowestPriorityOwnerId = ownerId;
-                currentLowestPriority = priority;
-                isRequestFromSameProcess = (requestClient.getProcessId()
-                    == (getClientProfile(ownerId)).getProcessId());
-            }
-        }
-
-        // When all the CiCam sessions are occupied, reclaim the lowest priority client if the
-        // request client has higher priority.
-        if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority)
-            || ((requestClient.getPriority() == currentLowestPriority)
-                && isRequestFromSameProcess))) {
-            if (!reclaimResource(lowestPriorityOwnerId,
+        if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+            if (!reclaimResource(reclaimOwnerId[0],
                     TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM)) {
                 return false;
             }
-            ciCamHandle[0] = generateResourceHandle(
-                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId());
-            updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
-            return true;
+            synchronized (mLock) {
+                if (getCiCamResource(request.ciCamId).isFullyUsed()) {
+                    Slog.e(TAG, "Reclaimed ciCam still fully used");
+                    return false;
+                }
+                updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
+            }
         }
+        return true;
+    }
+
+    protected boolean claimCiCam(TunerCiCamRequest request, int[] ciCamHandle,
+            int[] reclaimOwnerId) throws RemoteException {
+        ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        reclaimOwnerId[0] = INVALID_CLIENT_ID;
+        synchronized (mLock) {
+            if (!checkClientExists(request.clientId)) {
+                throw new RemoteException("Request ciCam from unregistered client:"
+                        + request.clientId);
+            }
+            CiCamResource ciCam = getCiCamResource(request.ciCamId);
+            // Unregistered CiCam is treated as having unlimited sessions.
+            if (ciCam == null) {
+                int resourceHandle = generateResourceHandle(
+                        TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, request.ciCamId);
+                ciCam = new CiCamResource.Builder(resourceHandle, request.ciCamId)
+                                    .maxSessionNum(Integer.MAX_VALUE)
+                                    .build();
+                addCiCamResource(ciCam);
+            }
+            ClientProfile requestClient = getClientProfile(request.clientId);
+            clientPriorityUpdateOnRequest(requestClient);
+            int lowestPriorityOwnerId = INVALID_CLIENT_ID;
+            // Priority max value is 1000
+            int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+            boolean isRequestFromSameProcess = false;
+            if (!ciCam.isFullyUsed()) {
+                updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId);
+                ciCamHandle[0] = ciCam.getHandle();
+                return true;
+            }
+            for (int ownerId : ciCam.getOwnerClientIds()) {
+                // Record the client id with lowest priority that is using the current CiCam.
+                int priority = updateAndGetOwnerClientPriority(ownerId);
+                if (currentLowestPriority > priority) {
+                    lowestPriorityOwnerId = ownerId;
+                    currentLowestPriority = priority;
+                    isRequestFromSameProcess = (requestClient.getProcessId()
+                        == (getClientProfile(ownerId)).getProcessId());
+                }
+            }
+
+            // When all the CiCam sessions are occupied, reclaim the lowest priority client if the
+            // request client has higher priority.
+            if (lowestPriorityOwnerId != INVALID_CLIENT_ID
+                    && ((requestClient.getPriority() > currentLowestPriority)
+                    || ((requestClient.getPriority() == currentLowestPriority)
+                    && isRequestFromSameProcess))) {
+                ciCamHandle[0] = ciCam.getHandle();
+                reclaimOwnerId[0] = lowestPriorityOwnerId;
+                return true;
+            }
+        }
+
         return false;
     }
 
@@ -1383,20 +1454,49 @@
     }
 
     @VisibleForTesting
-    protected void releaseFrontendInternal(FrontendResource fe, int clientId) {
+    protected void releaseFrontendInternal(int frontendHandle, int clientId)
+            throws RemoteException {
         if (DEBUG) {
-            Slog.d(TAG, "releaseFrontend(id=" + fe.getHandle() + ", clientId=" + clientId + " )");
+            Slog.d(TAG, "releaseFrontend(id=" + frontendHandle + ", clientId=" + clientId + " )");
         }
-        if (clientId == fe.getOwnerClientId()) {
-            ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
-            if (ownerClient != null) {
-                for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
-                    reclaimResource(shareOwnerId,
-                            TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+        if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
+                frontendHandle)) {
+            throw new RemoteException("frontendHandle can't be invalid");
+        }
+        Set<Integer> reclaimedResourceOwnerIds = unclaimFrontend(frontendHandle, clientId);
+        if (reclaimedResourceOwnerIds != null) {
+            for (int shareOwnerId : reclaimedResourceOwnerIds) {
+                reclaimResource(shareOwnerId,
+                        TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+            }
+        }
+        synchronized (mLock) {
+            clearFrontendAndClientMapping(getClientProfile(clientId));
+        }
+    }
+
+    private Set<Integer> unclaimFrontend(int frontendHandle, int clientId) throws RemoteException {
+        Set<Integer> reclaimedResourceOwnerIds = null;
+        synchronized (mLock) {
+            if (!checkClientExists(clientId)) {
+                throw new RemoteException("Release frontend from unregistered client:"
+                        + clientId);
+            }
+            FrontendResource fe = getFrontendResource(frontendHandle);
+            if (fe == null) {
+                throw new RemoteException("Releasing frontend does not exist.");
+            }
+            int ownerClientId = fe.getOwnerClientId();
+            ClientProfile ownerProfile = getClientProfile(ownerClientId);
+            if (ownerClientId == clientId) {
+                reclaimedResourceOwnerIds = ownerProfile.getShareFeClientIds();
+            } else {
+                if (!ownerProfile.getShareFeClientIds().contains(clientId)) {
+                    throw new RemoteException("Client is not a sharee of the releasing fe.");
                 }
             }
         }
-        clearFrontendAndClientMapping(getClientProfile(clientId));
+        return reclaimedResourceOwnerIds;
     }
 
     @VisibleForTesting
@@ -1432,103 +1532,129 @@
     }
 
     @VisibleForTesting
-    protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
+    public boolean requestDemuxInternal(@NonNull TunerDemuxRequest request,
+                @NonNull int[] demuxHandle) throws RemoteException {
         if (DEBUG) {
             Slog.d(TAG, "requestDemux(request=" + request + ")");
         }
-
-        // For Tuner 2.0 and below or any HW constraint devices that are unable to support
-        // ITuner.openDemuxById(), demux resources are not really managed under TRM and
-        // mDemuxResources.size() will be zero
-        if (mDemuxResources.size() == 0) {
-            // There are enough Demux resources, so we don't manage Demux in R.
-            demuxHandle[0] =
-                    generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
-            return true;
-        }
-
-        demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        ClientProfile requestClient = getClientProfile(request.clientId);
-
-        if (requestClient == null) {
+        int[] reclaimOwnerId = new int[1];
+        if (!claimDemux(request, demuxHandle, reclaimOwnerId)) {
             return false;
         }
+        if (demuxHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
+            return false;
+        }
+        if (reclaimOwnerId[0] != INVALID_CLIENT_ID) {
+            if (!reclaimResource(reclaimOwnerId[0],
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
+                return false;
+            }
+            synchronized (mLock) {
+                if (getDemuxResource(demuxHandle[0]).isInUse()) {
+                    Slog.e(TAG, "Reclaimed demux still in use");
+                    return false;
+                }
+                updateDemuxClientMappingOnNewGrant(demuxHandle[0], request.clientId);
+            }
+        }
+        return true;
+    }
 
-        clientPriorityUpdateOnRequest(requestClient);
-        int grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        // Priority max value is 1000
-        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
-        boolean isRequestFromSameProcess = false;
-        // If the desired demux id was specified, we only need to check the demux.
-        boolean hasDesiredDemuxCap = request.desiredFilterTypes
-                != DemuxFilterMainType.UNDEFINED;
-        int smallestNumOfSupportedCaps = Integer.SIZE + 1;
-        int smallestNumOfSupportedCapsInUse = Integer.SIZE + 1;
-        for (DemuxResource dr : getDemuxResources().values()) {
-            if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) {
-                if (!dr.isInUse()) {
-                    int numOfSupportedCaps = dr.getNumOfCaps();
+    protected boolean claimDemux(TunerDemuxRequest request, int[] demuxHandle, int[] reclaimOwnerId)
+            throws RemoteException {
+        demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        reclaimOwnerId[0] = INVALID_CLIENT_ID;
+        synchronized (mLock) {
+            if (!checkClientExists(request.clientId)) {
+                throw new RemoteException("Request demux from unregistered client:"
+                        + request.clientId);
+            }
 
-                    // look for the demux with minimum caps supporting the desired caps
-                    if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
-                        smallestNumOfSupportedCaps = numOfSupportedCaps;
-                        grantingDemuxHandle = dr.getHandle();
-                    }
-                } else if (grantingDemuxHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) {
-                    // Record the demux id with the lowest client priority among all the
-                    // in use demuxes when no availabledemux has been found.
-                    int priority = updateAndGetOwnerClientPriority(dr.getOwnerClientId());
-                    if (currentLowestPriority >= priority) {
+            // For Tuner 2.0 and below or any HW constraint devices that are unable to support
+            // ITuner.openDemuxById(), demux resources are not really managed under TRM and
+            // mDemuxResources.size() will be zero
+            if (mDemuxResources.size() == 0) {
+                // There are enough Demux resources, so we don't manage Demux in R.
+                demuxHandle[0] =
+                        generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0);
+                return true;
+            }
+
+            ClientProfile requestClient = getClientProfile(request.clientId);
+            if (requestClient == null) {
+                return false;
+            }
+            clientPriorityUpdateOnRequest(requestClient);
+            DemuxResource grantingDemux = null;
+            DemuxResource inUseLowestPriorityDemux = null;
+            // Priority max value is 1000
+            int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+            boolean isRequestFromSameProcess = false;
+            // If the desired demux id was specified, we only need to check the demux.
+            boolean hasDesiredDemuxCap = request.desiredFilterTypes
+                    != DemuxFilterMainType.UNDEFINED;
+            int smallestNumOfSupportedCaps = Integer.SIZE + 1;
+            int smallestNumOfSupportedCapsInUse = Integer.SIZE + 1;
+            for (DemuxResource dr : getDemuxResources().values()) {
+                if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) {
+                    if (!dr.isInUse()) {
                         int numOfSupportedCaps = dr.getNumOfCaps();
-                        boolean shouldUpdate = false;
-                        // update lowest priority
-                        if (currentLowestPriority > priority) {
-                            currentLowestPriority = priority;
-                            isRequestFromSameProcess = (requestClient.getProcessId()
-                                == (getClientProfile(dr.getOwnerClientId())).getProcessId());
 
-                            // reset the smallest caps when lower priority resource is found
-                            smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
-
-                            shouldUpdate = true;
-                        } else {
-                            // This is the case when the priority is the same as previously found
-                            // one. Update smallest caps when priority.
-                            if (smallestNumOfSupportedCapsInUse > numOfSupportedCaps) {
-                                smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
-                                shouldUpdate = true;
-                            }
+                        // look for the demux with minimum caps supporting the desired caps
+                        if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
+                            smallestNumOfSupportedCaps = numOfSupportedCaps;
+                            grantingDemux = dr;
                         }
-                        if (shouldUpdate) {
-                            inUseLowestPriorityDrHandle = dr.getHandle();
+                    } else if (grantingDemux == null) {
+                        // Record the demux id with the lowest client priority among all the
+                        // in use demuxes when no availabledemux has been found.
+                        int priority = updateAndGetOwnerClientPriority(dr.getOwnerClientId());
+                        if (currentLowestPriority >= priority) {
+                            int numOfSupportedCaps = dr.getNumOfCaps();
+                            boolean shouldUpdate = false;
+                            // update lowest priority
+                            if (currentLowestPriority > priority) {
+                                currentLowestPriority = priority;
+                                isRequestFromSameProcess = (requestClient.getProcessId()
+                                    == (getClientProfile(dr.getOwnerClientId())).getProcessId());
+
+                                // reset the smallest caps when lower priority resource is found
+                                smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
+
+                                shouldUpdate = true;
+                            } else {
+                                // This is the case when the priority is the same as previously
+                                // found one. Update smallest caps when priority.
+                                if (smallestNumOfSupportedCapsInUse > numOfSupportedCaps) {
+                                    smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
+                                    shouldUpdate = true;
+                                }
+                            }
+                            if (shouldUpdate) {
+                                inUseLowestPriorityDemux = dr;
+                            }
                         }
                     }
                 }
             }
-        }
 
-        // Grant demux when there is unused resource.
-        if (grantingDemuxHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
-            demuxHandle[0] = grantingDemuxHandle;
-            updateDemuxClientMappingOnNewGrant(grantingDemuxHandle, request.clientId);
-            return true;
-        }
-
-        // When all the resources are occupied, grant the lowest priority resource if the
-        // request client has higher priority.
-        if (inUseLowestPriorityDrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE
-            && ((requestClient.getPriority() > currentLowestPriority) || (
-            (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) {
-            if (!reclaimResource(
-                    getDemuxResource(inUseLowestPriorityDrHandle).getOwnerClientId(),
-                    TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
-                return false;
+            // Grant demux when there is unused resource.
+            if (grantingDemux != null) {
+                updateDemuxClientMappingOnNewGrant(grantingDemux.getHandle(), request.clientId);
+                demuxHandle[0] = grantingDemux.getHandle();
+                return true;
             }
-            demuxHandle[0] = inUseLowestPriorityDrHandle;
-            updateDemuxClientMappingOnNewGrant(
-                    inUseLowestPriorityDrHandle, request.clientId);
-            return true;
+
+            // When all the resources are occupied, grant the lowest priority resource if the
+            // request client has higher priority.
+            if (inUseLowestPriorityDemux != null
+                    && ((requestClient.getPriority() > currentLowestPriority) || (
+                    (requestClient.getPriority() == currentLowestPriority)
+                        && isRequestFromSameProcess))) {
+                demuxHandle[0] = inUseLowestPriorityDemux.getHandle();
+                reclaimOwnerId[0] = inUseLowestPriorityDemux.getOwnerClientId();
+                return true;
+            }
         }
 
         return false;
@@ -1792,7 +1918,9 @@
             return;
         }
 
-        mListeners.put(clientId, record);
+        synchronized (mLock) {
+            mListeners.put(clientId, record);
+        }
     }
 
     @VisibleForTesting
@@ -1808,33 +1936,44 @@
 
         // Reclaim all the resources of the share owners of the frontend that is used by the current
         // resource reclaimed client.
-        ClientProfile profile = getClientProfile(reclaimingClientId);
-        // TODO: check if this check is really needed.
-        if (profile == null) {
-            return true;
-        }
-        Set<Integer> shareFeClientIds = profile.getShareFeClientIds();
-        for (int clientId : shareFeClientIds) {
-            try {
-                mListeners.get(clientId).getListener().onReclaimResources();
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e);
-                return false;
+        Set<Integer> shareFeClientIds;
+        synchronized (mLock) {
+            ClientProfile profile = getClientProfile(reclaimingClientId);
+            if (profile == null) {
+                return true;
             }
-            clearAllResourcesAndClientMapping(getClientProfile(clientId));
+            shareFeClientIds = profile.getShareFeClientIds();
+        }
+        ResourcesReclaimListenerRecord listenerRecord = null;
+        for (int clientId : shareFeClientIds) {
+            synchronized (mLock) {
+                listenerRecord = mListeners.get(clientId);
+            }
+            if (listenerRecord != null) {
+                try {
+                    listenerRecord.getListener().onReclaimResources();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e);
+                }
+            }
         }
 
         if (DEBUG) {
             Slog.d(TAG, "Reclaiming resources because higher priority client request resource type "
                     + resourceType + ", clientId:" + reclaimingClientId);
         }
-        try {
-            mListeners.get(reclaimingClientId).getListener().onReclaimResources();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
-            return false;
+
+        synchronized (mLock) {
+            listenerRecord = mListeners.get(reclaimingClientId);
         }
-        clearAllResourcesAndClientMapping(profile);
+        if (listenerRecord != null) {
+            try {
+                listenerRecord.getListener().onReclaimResources();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e);
+            }
+        }
+
         return true;
     }
 
@@ -2258,6 +2397,7 @@
         addResourcesReclaimListener(clientId, listener);
     }
 
+    @SuppressWarnings("GuardedBy") // Lock is held on mListeners
     private void removeClientProfile(int clientId) {
         for (int shareOwnerId : getClientProfile(clientId).getShareFeClientIds()) {
             clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
@@ -2265,12 +2405,9 @@
         clearAllResourcesAndClientMapping(getClientProfile(clientId));
         mClientProfiles.remove(clientId);
 
-        // it may be called by unregisterClientProfileInternal under test
-        synchronized (mLock) {
-            ResourcesReclaimListenerRecord record = mListeners.remove(clientId);
-            if (record != null) {
-                record.getListener().asBinder().unlinkToDeath(record, 0);
-            }            
+        ResourcesReclaimListenerRecord record = mListeners.remove(clientId);
+        if (record != null) {
+            record.getListener().asBinder().unlinkToDeath(record, 0);
         }
     }
 
@@ -2304,7 +2441,8 @@
         profile.releaseFrontend();
     }
 
-    private void clearAllResourcesAndClientMapping(ClientProfile profile) {
+    @VisibleForTesting
+    protected void clearAllResourcesAndClientMapping(ClientProfile profile) {
         // TODO: check if this check is really needed. Maybe needed for reclaimResource path.
         if (profile == null) {
             return;
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/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index c4d601d..6740153 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -19,14 +19,12 @@
 import static android.webkit.Flags.updateServiceV2;
 
 import android.app.ActivityManager;
-import android.app.AppGlobals;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
 import android.content.res.XmlResourceParser;
 import android.os.Build;
 import android.os.RemoteException;
@@ -79,7 +77,7 @@
         XmlResourceParser parser = null;
         List<WebViewProviderInfo> webViewProviders = new ArrayList<WebViewProviderInfo>();
         try {
-            parser = AppGlobals.getInitialApplication().getResources().getXml(
+            parser = mContext.getResources().getXml(
                     com.android.internal.R.xml.config_webview_packages);
             XmlUtils.beginDocument(parser, TAG_START);
             while(true) {
@@ -148,7 +146,7 @@
     }
 
     public long getFactoryPackageVersion(String packageName) throws NameNotFoundException {
-        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+        PackageManager pm = mContext.getPackageManager();
         return pm.getPackageInfo(packageName, PackageManager.MATCH_FACTORY_ONLY)
                 .getLongVersionCode();
     }
@@ -203,47 +201,48 @@
     @Override
     public void enablePackageForAllUsers(String packageName, boolean enable) {
         UserManager userManager = mContext.getSystemService(UserManager.class);
-        for(UserInfo userInfo : userManager.getUsers()) {
-            enablePackageForUser(packageName, enable, userInfo.id);
+        for (UserHandle user : userManager.getUserHandles(false)) {
+            enablePackageForUser(packageName, enable, user);
         }
     }
 
-    private void enablePackageForUser(String packageName, boolean enable, int userId) {
+    private void enablePackageForUser(String packageName, boolean enable, UserHandle user) {
+        Context contextAsUser = mContext.createContextAsUser(user, 0);
+        PackageManager pm = contextAsUser.getPackageManager();
         try {
-            AppGlobals.getPackageManager().setApplicationEnabledSetting(
+            pm.setApplicationEnabledSetting(
                     packageName,
                     enable ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT :
-                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0,
-                    userId, null);
-        } catch (RemoteException | IllegalArgumentException e) {
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0);
+        } catch (IllegalArgumentException e) {
             Log.w(TAG, "Tried to " + (enable ? "enable " : "disable ") + packageName
-                    + " for user " + userId + ": " + e);
+                    + " for user " + user + ": " + e);
         }
     }
 
     @Override
     public void installExistingPackageForAllUsers(String packageName) {
         UserManager userManager = mContext.getSystemService(UserManager.class);
-        for (UserInfo userInfo : userManager.getUsers()) {
-            installPackageForUser(packageName, userInfo.id);
+        for (UserHandle user : userManager.getUserHandles(false)) {
+            installPackageForUser(packageName, user);
         }
     }
 
-    private void installPackageForUser(String packageName, int userId) {
-        final Context contextAsUser = mContext.createContextAsUser(UserHandle.of(userId), 0);
-        final PackageInstaller installer = contextAsUser.getPackageManager().getPackageInstaller();
+    private void installPackageForUser(String packageName, UserHandle user) {
+        Context contextAsUser = mContext.createContextAsUser(user, 0);
+        PackageInstaller installer = contextAsUser.getPackageManager().getPackageInstaller();
         installer.installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN, null);
     }
 
     @Override
     public boolean systemIsDebuggable() {
-        return Build.IS_DEBUGGABLE;
+        return Build.isDebuggable();
     }
 
     @Override
     public PackageInfo getPackageInfoForProvider(WebViewProviderInfo configInfo)
             throws NameNotFoundException {
-        PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+        PackageManager pm = mContext.getPackageManager();
         return pm.getPackageInfo(configInfo.packageName, PACKAGE_FLAGS);
     }
 
@@ -327,5 +326,5 @@
     // flags declaring we want extra info from the package manager for webview providers
     private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
             | PackageManager.GET_SIGNATURES | PackageManager.GET_SHARED_LIBRARY_FILES
-            | PackageManager.MATCH_DEBUG_TRIAGED_MISSING | PackageManager.MATCH_ANY_USER;
+            | PackageManager.MATCH_ANY_USER;
 }
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 10ce8c2..7cbacd6 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1764,15 +1764,7 @@
             IBinder topFocusedWindowToken = null;
 
             synchronized (mService.mGlobalLock) {
-                // If there is a recents animation running, then use the animation target as the
-                // top window state. Otherwise,do not send the windows if there is no top focus as
-                // the window manager is still looking for where to put it. We will do the work when
-                // we get a focus change callback.
-                final RecentsAnimationController controller =
-                        mService.getRecentsAnimationController();
-                final WindowState topFocusedWindowState = controller != null
-                        ? controller.getTargetAppMainWindow()
-                        : getTopFocusWindow();
+                final WindowState topFocusedWindowState = getTopFocusWindow();
                 if (topFocusedWindowState == null) {
                     if (DEBUG) {
                         Slog.d(LOG_TAG, "top focused window is null, compute it again later");
@@ -1907,10 +1899,6 @@
 
         private boolean windowMattersToAccessibility(AccessibilityWindow a11yWindow,
                 Region regionInScreen, Region unaccountedSpace) {
-            if (a11yWindow.ignoreRecentsAnimationForAccessibility()) {
-                return false;
-            }
-
             if (a11yWindow.isFocused()) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index d8e7c77..fd2a909 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -659,7 +659,6 @@
         private boolean mIsPIPMenu;
         private boolean mIsFocused;
         private boolean mShouldMagnify;
-        private boolean mIgnoreDuetoRecentsAnimation;
         private final Region mTouchableRegionInScreen = new Region();
         private final Region mTouchableRegionInWindow = new Region();
         private WindowInfo mWindowInfo;
@@ -692,10 +691,6 @@
             instance.mIsFocused = windowState != null && windowState.isFocused();
             instance.mShouldMagnify = windowState == null || windowState.shouldMagnify();
 
-            final RecentsAnimationController controller = service.getRecentsAnimationController();
-            instance.mIgnoreDuetoRecentsAnimation = windowState != null && controller != null
-                    && controller.shouldIgnoreForAccessibility(windowState);
-
             final Rect windowFrame = new Rect(inputWindowHandle.frame);
             getTouchableRegionInWindow(instance.mShouldMagnify, inputWindowHandle.touchableRegion,
                     instance.mTouchableRegionInWindow, windowFrame, magnificationInverseMatrix,
@@ -793,13 +788,6 @@
         }
 
         /**
-         * @return true if it's running the recent animation but not the target app.
-         */
-        public boolean ignoreRecentsAnimationForAccessibility() {
-            return mIgnoreDuetoRecentsAnimation;
-        }
-
-        /**
          * @return true if this window is the trusted overlay.
          */
         public boolean isTrustedOverlay() {
@@ -909,7 +897,6 @@
                     + ", privateFlag=0x" + Integer.toHexString(mPrivateFlags)
                     + ", focused=" + mIsFocused
                     + ", shouldMagnify=" + mShouldMagnify
-                    + ", ignoreDuetoRecentsAnimation=" + mIgnoreDuetoRecentsAnimation
                     + ", isTrustedOverlay=" + isTrustedOverlay()
                     + ", regionInScreen=" + mTouchableRegionInScreen
                     + ", touchableRegion=" + mTouchableRegionInWindow
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/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index fb2bf39..fb5c115 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -838,12 +838,13 @@
         }
 
         if (android.app.Flags.appStartInfoTimestamps()) {
+            final int pid = r.getPid();
             // Log here to match StatsD for time to first frame.
             mLoggerHandler.post(
                     () -> mSupervisor.mService.mWindowManager.mAmInternal.addStartInfoTimestamp(
                             ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME,
-                            timestampNs, r.getUid(), r.getPid(),
-                            info.mLastLaunchedActivity.mUserId));
+                            timestampNs, infoSnapshot.applicationInfo.uid, pid,
+                            infoSnapshot.userId));
         }
 
         return infoSnapshot;
@@ -1275,10 +1276,8 @@
         final ActivityRecord r = info.mLastLaunchedActivity;
         final long lastTopLossTime = r.topResumedStateLossTime;
         final WindowManagerService wm = mSupervisor.mService.mWindowManager;
-        final Object controller = wm.getRecentsAnimationController();
         mLoggerHandler.postDelayed(() -> {
-            if (lastTopLossTime != r.topResumedStateLossTime
-                    || controller != wm.getRecentsAnimationController()) {
+            if (lastTopLossTime != r.topResumedStateLossTime) {
                 // Skip if the animation was finished in a short time.
                 return;
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 530c03f..e562ea8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -109,8 +109,6 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15;
 import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -228,7 +226,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
 import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
-import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -236,7 +233,6 @@
 import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
 import static com.android.server.wm.TaskPersister.DEBUG;
@@ -341,7 +337,6 @@
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
-import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets;
@@ -358,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;
@@ -640,12 +636,6 @@
 
     private SizeConfigurationBuckets mSizeConfigurations;
 
-    /**
-     * The precomputed display insets for resolving configuration. It will be non-null if
-     * {@link #shouldCreateCompatDisplayInsets} returns {@code true}.
-     */
-    private CompatDisplayInsets mCompatDisplayInsets;
-
     @VisibleForTesting
     final TaskFragment.ConfigOverrideHint mResolveConfigHint;
 
@@ -795,22 +785,6 @@
     @NonNull
     final AppCompatController mAppCompatController;
 
-    /**
-     * The scale to fit at least one side of the activity to its parent. If the activity uses
-     * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5.
-     */
-    private float mSizeCompatScale = 1f;
-
-    /**
-     * The bounds in global coordinates for activity in size compatibility mode.
-     * @see ActivityRecord#hasSizeCompatBounds()
-     */
-    private Rect mSizeCompatBounds;
-
-    // Whether this activity is in size compatibility mode because its bounds don't fit in parent
-    // naturally.
-    private boolean mInSizeCompatModeForBounds = false;
-
     // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
     // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
     // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -839,6 +813,8 @@
     /** The last set {@link DropInputMode} for this activity surface. */
     @DropInputMode
     private int mLastDropInputMode = DropInputMode.NONE;
+    /** Whether the input to this activity will be dropped during the current playing animation. */
+    private boolean mIsInputDroppedForAnimation;
 
     /**
      * Whether the application has desk mode resources. Calculated and cached when
@@ -1260,10 +1236,6 @@
         if (mPendingRelaunchCount != 0) {
             pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount);
         }
-        if (mSizeCompatScale != 1f || mSizeCompatBounds != null) {
-            pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds="
-                    + mSizeCompatBounds);
-        }
         if (mRemovingFromDisplay) {
             pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay);
         }
@@ -1677,6 +1649,15 @@
         }
     }
 
+    /** Sets if all input will be dropped as a protection during the client-driven animation. */
+    void setDropInputForAnimation(boolean isInputDroppedForAnimation) {
+        if (mIsInputDroppedForAnimation == isInputDroppedForAnimation) {
+            return;
+        }
+        mIsInputDroppedForAnimation = isInputDroppedForAnimation;
+        updateUntrustedEmbeddingInputProtection();
+    }
+
     /**
      * Sets to drop input when obscured to activity if it is embedded in untrusted mode.
      *
@@ -1689,7 +1670,10 @@
         if (getSurfaceControl() == null) {
             return;
         }
-        if (isEmbeddedInUntrustedMode()) {
+        if (mIsInputDroppedForAnimation) {
+            // Disable all input during the animation.
+            setDropInputMode(DropInputMode.ALL);
+        } else if (isEmbeddedInUntrustedMode()) {
             // Set drop input to OBSCURED when untrusted embedded.
             setDropInputMode(DropInputMode.OBSCURED);
         } else {
@@ -2650,7 +2634,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()) {
@@ -4972,9 +4957,8 @@
         newIntents.add(intent);
     }
 
-    final boolean isSleeping() {
-        final Task rootTask = getRootTask();
-        return rootTask != null ? rootTask.shouldSleepActivities() : mAtmService.isSleepingLocked();
+    boolean isSleeping() {
+        return task != null ? task.shouldSleepActivities() : mAtmService.isSleepingLocked();
     }
 
     /**
@@ -4998,7 +4982,7 @@
         final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer),
                 callerToken);
         boolean unsent = true;
-        final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
+        final boolean isTopActivityWhileSleeping = isSleeping() && isTopRunningActivity();
 
         // We want to immediately deliver the intent to the activity if:
         // - It is currently resumed or paused. i.e. it is currently visible to the user and we want
@@ -5065,7 +5049,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);
@@ -5188,7 +5173,8 @@
         }
 
         if (options != null) {
-            mTransitionController.setOverrideAnimation(options, startCallback, finishCallback);
+            mTransitionController.setOverrideAnimation(options, this, startCallback,
+                    finishCallback);
         }
     }
 
@@ -5573,10 +5559,7 @@
             return false;
         }
         if (!mDisplayContent.mAppTransition.isTransitionSet()) {
-            // Defer committing visibility for non-home app which is animating by recents.
-            if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
-                return false;
-            }
+            return false;
         }
         if (mWaitForEnteringPinnedMode && mVisible == visible) {
             // If the visibility is not changed during enter PIP, we don't want to include it in
@@ -5730,8 +5713,7 @@
     private void postApplyAnimation(boolean visible, boolean fromTransition) {
         final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
         final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
-                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
-                        | ANIMATION_TYPE_RECENTS);
+                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION);
         if (!delayed && !usingShellTransitions) {
             // We aren't delayed anything, but exiting windows rely on the animation finished
             // callback being called in case the ActivityRecord was pretending to be delayed,
@@ -5753,7 +5735,7 @@
         // animation and aren't in RESUMED state. Otherwise, we'll update client visibility in
         // onAnimationFinished or activityStopped.
         if (visible || (mState != RESUMED && (usingShellTransitions || !isAnimating(
-                PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)))) {
+                PARENTS, ANIMATION_TYPE_APP_TRANSITION)))) {
             setClientVisible(visible);
         }
 
@@ -5865,7 +5847,7 @@
 
     void setState(State state, String reason) {
         ProtoLog.v(WM_DEBUG_STATES, "State movement: %s from:%s to:%s reason:%s",
-                this, getState(), state, reason);
+                this, mState, state, reason);
 
         if (state == mState) {
             // No need to do anything if state doesn't change.
@@ -5925,6 +5907,7 @@
                     mAtmService.updateBatteryStats(this, false);
                 }
                 mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_DESTROYED);
+                idle = false;
                 // Fall through.
             case DESTROYING:
                 if (app != null && !app.hasActivities()) {
@@ -6190,7 +6173,7 @@
         // Now for any activities that aren't visible to the user, make sure they no longer are
         // keeping the screen frozen.
         if (DEBUG_VISIBILITY) {
-            Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + getState());
+            Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + mState);
         }
         try {
             final boolean canEnterPictureInPicture = checkEnterPictureInPictureState(
@@ -6206,7 +6189,7 @@
             }
             setVisibility(false);
 
-            switch (getState()) {
+            switch (mState) {
                 case STOPPING:
                 case STOPPED:
                     // Reset the flag indicating that an app can enter picture-in-picture once the
@@ -6442,7 +6425,7 @@
         mTaskSupervisor.mStoppingActivities.remove(this);
         if (getDisplayArea().allResumedActivitiesComplete()) {
             // Construct the compat environment at a relatively stable state if needed.
-            updateCompatDisplayInsets();
+            mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets();
             mRootWindowContainer.executeAppTransitionForAllDisplay();
         }
 
@@ -7731,7 +7714,7 @@
                 // Ensure that the activity content is hidden when the decor surface is boosted to
                 // prevent UI redressing attack.
                 && !isDecorSurfaceBoosted)
-                || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+                || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION
                         | ANIMATION_TYPE_PREDICT_BACK);
 
         if (mSurfaceControl != null) {
@@ -8074,8 +8057,8 @@
         if (getRequestedConfigurationOrientation(false, requestedOrientation)
                     != getRequestedConfigurationOrientation(false /*forDisplay */)) {
             // Do not change the requested configuration now, because this will be done when setting
-            // the orientation below with the new mCompatDisplayInsets
-            clearSizeCompatModeAttributes();
+            // the orientation below with the new mAppCompatDisplayInsets
+            mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatModeAttributes();
         }
         ProtoLog.v(WM_DEBUG_ORIENTATION,
                 "Setting requested orientation %s for %s",
@@ -8169,6 +8152,9 @@
      */
     @Override
     protected int getOverrideOrientation() {
+        if (mWmService.mConstants.mIgnoreActivityOrientationRequest) {
+            return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+        }
         return mAppCompatController.getOrientationPolicy()
                 .overrideOrientationIfNeeded(super.getOverrideOrientation());
     }
@@ -8207,19 +8193,8 @@
     }
 
     @Nullable
-    CompatDisplayInsets getCompatDisplayInsets() {
-        if (mAppCompatController.getTransparentPolicy().isRunning()) {
-            return mAppCompatController.getTransparentPolicy().getInheritedCompatDisplayInsets();
-        }
-        return mCompatDisplayInsets;
-    }
-
-    /**
-     * @return The {@code true} if the current instance has {@link mCompatDisplayInsets} without
-     * considering the inheritance implemented in {@link #getCompatDisplayInsets()}
-     */
-    boolean hasCompatDisplayInsetsWithoutInheritance() {
-        return mCompatDisplayInsets != null;
+    AppCompatDisplayInsets getAppCompatDisplayInsets() {
+        return mAppCompatController.getAppCompatSizeCompatModePolicy().getAppCompatDisplayInsets();
     }
 
     /**
@@ -8227,10 +8202,12 @@
      *         density than its parent or its bounds don't fit in parent naturally.
      */
     boolean inSizeCompatMode() {
-        if (mInSizeCompatModeForBounds) {
+        final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController
+                .getAppCompatSizeCompatModePolicy();
+        if (scmPolicy.isInSizeCompatModeForBounds()) {
             return true;
         }
-        if (getCompatDisplayInsets() == null || !shouldCreateCompatDisplayInsets()
+        if (getAppCompatDisplayInsets() == null || !shouldCreateAppCompatDisplayInsets()
                 // The orientation is different from parent when transforming.
                 || isFixedRotationTransforming()) {
             return false;
@@ -8256,13 +8233,13 @@
      * Indicates the activity will keep the bounds and screen configuration when it was first
      * launched, no matter how its parent changes.
      *
-     * <p>If {@true}, then {@link CompatDisplayInsets} will be created in {@link
+     * <p>If {@true}, then {@link AppCompatDisplayInsets} will be created in {@link
      * #resolveOverrideConfiguration} to "freeze" activity bounds and insets.
      *
      * @return {@code true} if this activity is declared as non-resizable and fixed orientation or
      *         aspect ratio.
      */
-    boolean shouldCreateCompatDisplayInsets() {
+    boolean shouldCreateAppCompatDisplayInsets() {
         if (mAppCompatController.getAppCompatAspectRatioOverrides().hasFullscreenOverride()) {
             // If the user has forced the applications aspect ratio to be fullscreen, don't use size
             // compatibility mode in any situation. The user has been warned and therefore accepts
@@ -8284,7 +8261,7 @@
         final TaskDisplayArea tda = getTaskDisplayArea();
         if (inMultiWindowMode() || (tda != null && tda.inFreeformWindowingMode())) {
             final ActivityRecord root = task != null ? task.getRootActivity() : null;
-            if (root != null && root != this && !root.shouldCreateCompatDisplayInsets()) {
+            if (root != null && root != this && !root.shouldCreateAppCompatDisplayInsets()) {
                 // If the root activity doesn't use size compatibility mode, the activities above
                 // are forced to be the same for consistent visual appearance.
                 return false;
@@ -8324,69 +8301,7 @@
 
     @Override
     boolean hasSizeCompatBounds() {
-        return mSizeCompatBounds != null;
-    }
-
-    // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
-    private void updateCompatDisplayInsets() {
-        if (getCompatDisplayInsets() != null || !shouldCreateCompatDisplayInsets()) {
-            // The override configuration is set only once in size compatibility mode.
-            return;
-        }
-
-        Configuration overrideConfig = getRequestedOverrideConfiguration();
-        final Configuration fullConfig = getConfiguration();
-
-        // Ensure the screen related fields are set. It is used to prevent activity relaunch
-        // when moving between displays. For screenWidthDp and screenWidthDp, because they
-        // are relative to bounds and density, they will be calculated in
-        // {@link Task#computeConfigResourceOverrides} and the result will also be
-        // relatively fixed.
-        overrideConfig.colorMode = fullConfig.colorMode;
-        overrideConfig.densityDpi = fullConfig.densityDpi;
-        // The smallest screen width is the short side of screen bounds. Because the bounds
-        // and density won't be changed, smallestScreenWidthDp is also fixed.
-        overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
-        if (ActivityInfo.isFixedOrientation(getOverrideOrientation())) {
-            // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
-            // apply runtime rotation changes.
-            overrideConfig.windowConfiguration.setRotation(
-                    fullConfig.windowConfiguration.getRotation());
-        }
-
-        final Rect letterboxedContainerBounds = mAppCompatController
-                .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
-
-        // The role of CompatDisplayInsets is like the override bounds.
-        mCompatDisplayInsets =
-                new CompatDisplayInsets(
-                        mDisplayContent, this, letterboxedContainerBounds,
-                        mResolveConfigHint.mUseOverrideInsetsForConfig);
-    }
-
-    private void clearSizeCompatModeAttributes() {
-        mInSizeCompatModeForBounds = false;
-        final float lastSizeCompatScale = mSizeCompatScale;
-        mSizeCompatScale = 1f;
-        if (mSizeCompatScale != lastSizeCompatScale) {
-            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
-        }
-        mSizeCompatBounds = null;
-        mCompatDisplayInsets = null;
-        mAppCompatController.getTransparentPolicy().clearInheritedCompatDisplayInsets();
-    }
-
-    @VisibleForTesting
-    void clearSizeCompatMode() {
-        clearSizeCompatModeAttributes();
-        // Clear config override in #updateCompatDisplayInsets().
-        final int activityType = getActivityType();
-        final Configuration overrideConfig = getRequestedOverrideConfiguration();
-        overrideConfig.unset();
-        // Keep the activity type which was set when attaching to a task to prevent leaving it
-        // undefined.
-        overrideConfig.windowConfiguration.setActivityType(activityType);
-        onRequestedOverrideConfigurationChanged(overrideConfig);
+        return mAppCompatController.getAppCompatSizeCompatModePolicy().hasSizeCompatBounds();
     }
 
     @Override
@@ -8404,7 +8319,9 @@
 
     @Override
     float getCompatScale() {
-        return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
+        // We need to invoke {#getCompatScale()} only if the CompatScale is not available.
+        return mAppCompatController.getAppCompatSizeCompatModePolicy()
+                .getCompatScaleIfAvailable(ActivityRecord.super::getCompatScale);
     }
 
     @Override
@@ -8471,9 +8388,12 @@
                     .hasFullscreenOverride()) {
             resolveAspectRatioRestriction(newParentConfiguration);
         }
-        final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
-        if (compatDisplayInsets != null) {
-            resolveSizeCompatModeConfiguration(newParentConfiguration, compatDisplayInsets);
+        final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets();
+        final AppCompatSizeCompatModePolicy scmPolicy =
+                mAppCompatController.getAppCompatSizeCompatModePolicy();
+        if (appCompatDisplayInsets != null) {
+            scmPolicy.resolveSizeCompatModeConfiguration(newParentConfiguration,
+                    appCompatDisplayInsets, mTmpBounds);
         } else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
             // We ignore activities' requested orientation in multi-window modes. They may be
             // taken into consideration in resolveFixedOrientationConfiguration call above.
@@ -8491,13 +8411,14 @@
         if (!Flags.immersiveAppRepositioning()
                 && !mAppCompatController.getAppCompatAspectRatioPolicy()
                     .isLetterboxedForFixedOrientationAndAspectRatio()
-                && !mInSizeCompatModeForBounds
+                && !scmPolicy.isInSizeCompatModeForBounds()
                 && !mAppCompatController.getAppCompatAspectRatioOverrides()
                     .hasFullscreenOverride()) {
             resolveAspectRatioRestriction(newParentConfiguration);
         }
 
-        if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null
+        if (isFixedOrientationLetterboxAllowed
+                || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
                 // In fullscreen, can be letterboxed for aspect ratio.
                 || !inMultiWindowMode()) {
             updateResolvedBoundsPosition(newParentConfiguration);
@@ -8505,8 +8426,8 @@
 
         boolean isIgnoreOrientationRequest = mDisplayContent != null
                 && mDisplayContent.getIgnoreOrientationRequest();
-        if (compatDisplayInsets == null
-                // for size compat mode set in updateCompatDisplayInsets
+        if (!scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
+                // for size compat mode set in updateAppCompatDisplayInsets
                 // Fixed orientation letterboxing is possible on both large screen devices
                 // with ignoreOrientationRequest enabled and on phones in split screen even with
                 // ignoreOrientationRequest disabled.
@@ -8533,7 +8454,7 @@
         getResolvedOverrideConfiguration().seq = mConfigurationSeq;
 
         // Sandbox max bounds by setting it to the activity bounds, if activity is letterboxed, or
-        // has or will have mCompatDisplayInsets for size compat. Also forces an activity to be
+        // has or will have mAppCompatDisplayInsets for size compat. Also forces an activity to be
         // sandboxed or not depending upon the configuration settings.
         if (providesMaxBounds()) {
             mTmpBounds.set(resolvedConfig.windowConfiguration.getBounds());
@@ -8554,8 +8475,8 @@
                         info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
                         info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
                         !matchParentBounds(),
-                        compatDisplayInsets != null,
-                        shouldCreateCompatDisplayInsets());
+                        scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance(),
+                        shouldCreateAppCompatDisplayInsets());
             }
             resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
         }
@@ -8567,7 +8488,8 @@
                 resolvedConfig,
                 mOptOutEdgeToEdge,
                 hasFixedRotationTransform(),
-                getCompatDisplayInsets() != null);
+                getAppCompatDisplayInsets() != null,
+                task);
         mResolveConfigHint.resetTmpOverrides();
 
         logAppCompatState();
@@ -8577,7 +8499,7 @@
         return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride);
     }
 
-    private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
+    void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
             @NonNull Configuration parentConfig) {
         task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
         // Reset the temp info which should only take effect for the specified computation.
@@ -8629,7 +8551,9 @@
         if (mAppCompatController.getTransparentPolicy().isRunning()) {
             return mAppCompatController.getTransparentPolicy().getInheritedAppCompatState();
         }
-        if (mInSizeCompatModeForBounds) {
+        final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController
+                .getAppCompatSizeCompatModePolicy();
+        if (scmPolicy.isInSizeCompatModeForBounds()) {
             return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
         }
         // Letterbox for fixed orientation. This check returns true only when an activity is
@@ -8665,8 +8589,9 @@
         if (resolvedBounds.isEmpty()) {
             return;
         }
-        final Rect screenResolvedBounds =
-                mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
+        final AppCompatSizeCompatModePolicy scmPolicy =
+                mAppCompatController.getAppCompatSizeCompatModePolicy();
+        final Rect screenResolvedBounds = scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
         final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride;
         final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
         final float screenResolvedBoundsWidth = screenResolvedBounds.width();
@@ -8696,7 +8621,7 @@
                 offsetX = Math.max(0, (int) Math.ceil((appWidth
                         - screenResolvedBoundsWidth) * positionMultiplier)
                         // This is added to make sure that insets added inside
-                        // CompatDisplayInsets#getContainerBounds() do not break the alignment
+                        // AppCompatDisplayInsets#getContainerBounds() do not break the alignment
                         // provided by the positionMultiplier
                         - screenResolvedBounds.left + parentAppBounds.left);
             }
@@ -8717,19 +8642,15 @@
                 offsetY = Math.max(0, (int) Math.ceil((appHeight
                         - screenResolvedBoundsHeight) * positionMultiplier)
                         // This is added to make sure that insets added inside
-                        // CompatDisplayInsets#getContainerBounds() do not break the alignment
+                        // AppCompatDisplayInsets#getContainerBounds() do not break the alignment
                         // provided by the positionMultiplier
                         - screenResolvedBounds.top + parentAppBounds.top);
             }
         }
-
-        if (mSizeCompatBounds != null) {
-            mSizeCompatBounds.offset(offsetX , offsetY);
-            final int dy = mSizeCompatBounds.top - resolvedBounds.top;
-            final int dx = mSizeCompatBounds.left - resolvedBounds.left;
-            offsetBounds(resolvedConfig, dx, dy);
-        } else {
-            offsetBounds(resolvedConfig, offsetX, offsetY);
+        // If in SCM, apply offset to resolved bounds relative to size compat bounds. If
+        // not, apply directly to resolved bounds.
+        if (!scmPolicy.applyOffsetIfNeeded(resolvedBounds, resolvedConfig, offsetX, offsetY)) {
+            AppCompatUtils.offsetBounds(resolvedConfig, offsetX, offsetY);
         }
 
         // If the top is aligned with parentAppBounds add the vertical insets back so that the app
@@ -8737,9 +8658,7 @@
         if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top
                 && !isImmersiveMode) {
             resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top;
-            if (mSizeCompatBounds != null) {
-                mSizeCompatBounds.top = parentBounds.top;
-            }
+            scmPolicy.alignToTopIfNeeded(parentBounds);
         }
 
         // Since bounds has changed, the configuration needs to be computed accordingly.
@@ -8749,13 +8668,7 @@
         // easier to resolve the relative position in parent container. However, if the activity is
         // scaled, the position should follow the scale because the configuration will be sent to
         // the client which is expected to be in a scaled environment.
-        if (mSizeCompatScale != 1f) {
-            final int screenPosX = resolvedBounds.left;
-            final int screenPosY = resolvedBounds.top;
-            final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX;
-            final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY;
-            offsetBounds(resolvedConfig, dx, dy);
-        }
+        scmPolicy.applySizeCompatScaleIfNeeded(resolvedBounds, resolvedConfig);
     }
 
     boolean isImmersiveMode(@NonNull Rect parentBounds) {
@@ -8777,7 +8690,9 @@
     @NonNull Rect getScreenResolvedBounds() {
         final Configuration resolvedConfig = getResolvedOverrideConfiguration();
         final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
-        return mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
+        final AppCompatSizeCompatModePolicy scmPolicy =
+                mAppCompatController.getAppCompatSizeCompatModePolicy();
+        return scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
     }
 
     void recomputeConfiguration() {
@@ -8928,10 +8843,12 @@
                 || orientationRespectedWithInsets)) {
             return;
         }
-        final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
+        final AppCompatDisplayInsets mAppCompatDisplayInsets = getAppCompatDisplayInsets();
+        final AppCompatSizeCompatModePolicy scmPolicy =
+                mAppCompatController.getAppCompatSizeCompatModePolicy();
 
-        if (compatDisplayInsets != null
-                && !compatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
+        if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()
+                && !mAppCompatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
             // App prefers to keep its original size.
             // If the size compat is from previous fixed orientation letterboxing, we may want to
             // have fixed orientation letterbox again, otherwise it will show the size compat
@@ -8983,8 +8900,8 @@
                 .applyDesiredAspectRatio(newParentConfig, parentBounds, resolvedBounds,
                         containingBoundsWithInsets, containingBounds);
 
-        if (compatDisplayInsets != null) {
-            compatDisplayInsets.getBoundsByRotation(mTmpBounds,
+        if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()) {
+            mAppCompatDisplayInsets.getBoundsByRotation(mTmpBounds,
                     newParentConfig.windowConfiguration.getRotation());
             if (resolvedBounds.width() != mTmpBounds.width()
                     || resolvedBounds.height() != mTmpBounds.height()) {
@@ -9007,7 +8924,7 @@
 
         // Calculate app bounds using fixed orientation bounds because they will be needed later
         // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
-        mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+        mResolveConfigHint.mTmpCompatInsets = mAppCompatDisplayInsets;
         computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig);
         mAppCompatController.getAppCompatAspectRatioPolicy()
                 .setLetterboxBoundsForFixedOrientationAndAspectRatio(new Rect(resolvedBounds));
@@ -9044,246 +8961,15 @@
         }
     }
 
-    /**
-     * Resolves consistent screen configuration for orientation and rotation changes without
-     * inheriting the parent bounds.
-     */
-    private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration,
-            @NonNull CompatDisplayInsets compatDisplayInsets) {
-        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
-        final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
-
-        // When an activity needs to be letterboxed because of fixed orientation, use fixed
-        // orientation bounds (stored in resolved bounds) instead of parent bounds since the
-        // activity will be displayed within them even if it is in size compat mode. They should be
-        // saved here before resolved bounds are overridden below.
-        final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
-                ? mAppCompatController.getAppCompatAspectRatioPolicy()
-                    .isAspectRatioApplied()
-                : mAppCompatController.getAppCompatAspectRatioPolicy()
-                    .isLetterboxedForFixedOrientationAndAspectRatio();
-        final Rect containerBounds = useResolvedBounds
-                ? new Rect(resolvedBounds)
-                : newParentConfiguration.windowConfiguration.getBounds();
-        final Rect containerAppBounds = useResolvedBounds
-                ? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
-                : mResolveConfigHint.mParentAppBoundsOverride;
-
-        final int requestedOrientation = getRequestedConfigurationOrientation();
-        final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
-        final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig
-                ? mResolveConfigHint.mTmpOverrideConfigOrientation
-                : newParentConfiguration.orientation;
-        final int orientation = orientationRequested
-                ? requestedOrientation
-                // We should use the original orientation of the activity when possible to avoid
-                // forcing the activity in the opposite orientation.
-                : compatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
-                        ? compatDisplayInsets.mOriginalRequestedOrientation
-                        : parentOrientation;
-        int rotation = newParentConfiguration.windowConfiguration.getRotation();
-        final boolean isFixedToUserRotation = mDisplayContent == null
-                || mDisplayContent.getDisplayRotation().isFixedToUserRotation();
-        if (!isFixedToUserRotation && !compatDisplayInsets.mIsFloating) {
-            // Use parent rotation because the original display can be rotated.
-            resolvedConfig.windowConfiguration.setRotation(rotation);
-        } else {
-            final int overrideRotation = resolvedConfig.windowConfiguration.getRotation();
-            if (overrideRotation != ROTATION_UNDEFINED) {
-                rotation = overrideRotation;
-            }
-        }
-
-        // Use compat insets to lock width and height. We should not use the parent width and height
-        // because apps in compat mode should have a constant width and height. The compat insets
-        // are locked when the app is first launched and are never changed after that, so we can
-        // rely on them to contain the original and unchanging width and height of the app.
-        final Rect containingAppBounds = new Rect();
-        final Rect containingBounds = mTmpBounds;
-        compatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
-                orientation, orientationRequested, isFixedToUserRotation);
-        resolvedBounds.set(containingBounds);
-        // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
-        if (!compatDisplayInsets.mIsFloating) {
-            mAppCompatController.getAppCompatAspectRatioPolicy()
-                    .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
-                            containingBounds);
-        }
-
-        // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
-        // are calculated in compat container space. The actual position on screen will be applied
-        // later, so the calculation is simpler that doesn't need to involve offset from parent.
-        mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
-        computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
-        // Use current screen layout as source because the size of app is independent to parent.
-        resolvedConfig.screenLayout = computeScreenLayout(
-                getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
-                resolvedConfig.screenHeightDp);
-
-        // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside
-        // the parent bounds appropriately.
-        if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) {
-            resolvedConfig.orientation = parentOrientation;
-        }
-
-        // Below figure is an example that puts an activity which was launched in a larger container
-        // into a smaller container.
-        //   The outermost rectangle is the real display bounds.
-        //   "@" is the container app bounds (parent bounds or fixed orientation bounds)
-        //   "#" is the {@code resolvedBounds} that applies to application.
-        //   "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
-        // ------------------------------
-        // |                            |
-        // |    @@@@*********@@@@###    |
-        // |    @   *       *   @  #    |
-        // |    @   *       *   @  #    |
-        // |    @   *       *   @  #    |
-        // |    @@@@*********@@@@  #    |
-        // ---------#--------------#-----
-        //          #              #
-        //          ################
-        // The application is still layouted in "#" since it was launched, and it will be visually
-        // scaled and positioned to "*".
-
-        final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
-
-        // Calculates the scale the size compatibility bounds into the region which is available
-        // to application.
-        final float lastSizeCompatScale = mSizeCompatScale;
-        updateSizeCompatScale(resolvedAppBounds, containerAppBounds);
-
-        final int containerTopInset = containerAppBounds.top - containerBounds.top;
-        final boolean topNotAligned =
-                containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
-        if (mSizeCompatScale != 1f || topNotAligned) {
-            if (mSizeCompatBounds == null) {
-                mSizeCompatBounds = new Rect();
-            }
-            mSizeCompatBounds.set(resolvedAppBounds);
-            mSizeCompatBounds.offsetTo(0, 0);
-            mSizeCompatBounds.scale(mSizeCompatScale);
-            // The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
-            mSizeCompatBounds.bottom += containerTopInset;
-        } else {
-            mSizeCompatBounds = null;
-        }
-        if (mSizeCompatScale != lastSizeCompatScale) {
-            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
-        }
-
-        // The position will be later adjusted in updateResolvedBoundsPosition.
-        // Above coordinates are in "@" space, now place "*" and "#" to screen space.
-        final boolean fillContainer = resolvedBounds.equals(containingBounds);
-        final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
-        final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
-
-        if (screenPosX != 0 || screenPosY != 0) {
-            if (mSizeCompatBounds != null) {
-                mSizeCompatBounds.offset(screenPosX, screenPosY);
-            }
-            // Add the global coordinates and remove the local coordinates.
-            final int dx = screenPosX - resolvedBounds.left;
-            final int dy = screenPosY - resolvedBounds.top;
-            offsetBounds(resolvedConfig, dx, dy);
-        }
-
-        mInSizeCompatModeForBounds =
-                isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds);
-    }
-
-    void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
-        mSizeCompatScale = mAppCompatController.getTransparentPolicy()
-                .findOpaqueNotFinishingActivityBelow()
-                .map(activityRecord -> activityRecord.mSizeCompatScale)
-                .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
-    }
-
-    private float calculateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) {
-        final int contentW = resolvedAppBounds.width();
-        final int contentH = resolvedAppBounds.height();
-        final int viewportW = containerAppBounds.width();
-        final int viewportH = containerAppBounds.height();
-        // Allow an application to be up-scaled if its window is smaller than its
-        // original container or if it's a freeform window in desktop mode.
-        boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
-                || (canEnterDesktopMode(mAtmService.mContext)
-                    && getWindowingMode() == WINDOWING_MODE_FREEFORM);
-        return shouldAllowUpscaling ? Math.min(
-                (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
-    }
-
-    private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
-        if (mAppCompatController.getTransparentPolicy().isRunning()) {
-            // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
-            // is letterboxed.
-            return false;
-        }
-        final int appWidth = appBounds.width();
-        final int appHeight = appBounds.height();
-        final int containerAppWidth = containerBounds.width();
-        final int containerAppHeight = containerBounds.height();
-
-        if (containerAppWidth == appWidth && containerAppHeight == appHeight) {
-            // Matched the container bounds.
-            return false;
-        }
-        if (containerAppWidth > appWidth && containerAppHeight > appHeight) {
-            // Both sides are smaller than the container.
-            return true;
-        }
-        if (containerAppWidth < appWidth || containerAppHeight < appHeight) {
-            // One side is larger than the container.
-            return true;
-        }
-
-        // The rest of the condition is that only one side is smaller than the container, but it
-        // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
-        final float maxAspectRatio = getMaxAspectRatio();
-        if (maxAspectRatio > 0) {
-            final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
-                    / Math.min(appWidth, appHeight);
-            if (aspectRatio >= maxAspectRatio) {
-                // The current size has reached the max aspect ratio.
-                return false;
-            }
-        }
-        final float minAspectRatio = getMinAspectRatio();
-        if (minAspectRatio > 0) {
-            // The activity should have at least the min aspect ratio, so this checks if the
-            // container still has available space to provide larger aspect ratio.
-            final float containerAspectRatio =
-                    (0.5f + Math.max(containerAppWidth, containerAppHeight))
-                            / Math.min(containerAppWidth, containerAppHeight);
-            if (containerAspectRatio <= minAspectRatio) {
-                // The long side has reached the parent.
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /** @return The horizontal / vertical offset of putting the content in the center of viewport.*/
-    private static int getCenterOffset(int viewportDim, int contentDim) {
-        return (int) ((viewportDim - contentDim + 1) * 0.5f);
-    }
-
-    private static void offsetBounds(Configuration inOutConfig, int offsetX, int offsetY) {
-        inOutConfig.windowConfiguration.getBounds().offset(offsetX, offsetY);
-        inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY);
-    }
-
     @Override
     public Rect getBounds() {
         // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
         final Rect superBounds = super.getBounds();
+        final AppCompatSizeCompatModePolicy scmPolicy =
+                mAppCompatController.getAppCompatSizeCompatModePolicy();
         return mAppCompatController.getTransparentPolicy().findOpaqueNotFinishingActivityBelow()
                 .map(ActivityRecord::getBounds)
-                .orElseGet(() -> {
-                    if (mSizeCompatBounds != null) {
-                        return mSizeCompatBounds;
-                    }
-                    return superBounds;
-                });
+                .orElseGet(() -> scmPolicy.getAppSizeCompatBoundsIfAvailable(superBounds));
     }
 
     @Override
@@ -9305,13 +8991,13 @@
         if (info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig)) {
             return true;
         }
-        // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it
-        // will keep the same bounds and screen configuration when it was first launched regardless
-        // how its parent window changes, so that the sandbox API will provide a consistent result.
-        if (getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()) {
+        // Max bounds should be sandboxed when an activity should have mAppCompatDisplayInsets,
+        // and it will keep the same bounds and screen configuration when it was first launched
+        // regardless how its parent window changes, so that the sandbox API will provide a
+        // consistent result.
+        if (getAppCompatDisplayInsets() != null || shouldCreateAppCompatDisplayInsets()) {
             return true;
         }
-
         // No need to sandbox for resizable apps in (including in multi-window) because
         // resizableActivity=true indicates that they support multi-window. Likewise, do not sandbox
         // for activities in letterbox since the activity has declared it can handle resizing.
@@ -9362,7 +9048,7 @@
                 mTransitionController.collect(this);
             }
         }
-        if (getCompatDisplayInsets() != null) {
+        if (getAppCompatDisplayInsets() != null) {
             Configuration overrideConfig = getRequestedOverrideConfiguration();
             // Adapt to changes in orientation locking. The app is still non-resizable, but
             // it can change which orientation is fixed. If the fixed orientation changes,
@@ -9442,9 +9128,9 @@
         if (mVisibleRequested) {
             // It may toggle the UI for user to restart the size compatibility mode activity.
             display.handleActivitySizeCompatModeIfNeeded(this);
-        } else if (getCompatDisplayInsets() != null && !visibleIgnoringKeyguard
+        } else if (getAppCompatDisplayInsets() != null && !visibleIgnoringKeyguard
                 && (app == null || !app.hasVisibleActivities())) {
-            // visibleIgnoringKeyguard is checked to avoid clearing mCompatDisplayInsets during
+            // visibleIgnoringKeyguard is checked to avoid clearing mAppCompatDisplayInsets during
             // displays change. Displays are turned off during the change so mVisibleRequested
             // can be false.
             // The override changes can only be obtained from display, because we don't have the
@@ -9607,14 +9293,14 @@
 
         // Calling from here rather than from onConfigurationChanged because it's possible that
         // onConfigurationChanged was called before mVisibleRequested became true and
-        // mCompatDisplayInsets may not be called again when mVisibleRequested changes. And we
-        // don't want to save mCompatDisplayInsets in onConfigurationChanged without visibility
+        // mAppCompatDisplayInsets may not be called again when mVisibleRequested changes. And we
+        // don't want to save mAppCompatDisplayInsets in onConfigurationChanged without visibility
         // check to avoid remembering obsolete configuration which can lead to unnecessary
         // size-compat mode.
         if (mVisibleRequested) {
             // Calling from here rather than resolveOverrideConfiguration to ensure that this is
             // called after full config is updated in ConfigurationContainer#onConfigurationChanged.
-            updateCompatDisplayInsets();
+            mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets();
         }
 
         // Short circuit: if the two full configurations are equal (the common case), then there is
@@ -9954,7 +9640,7 @@
 
         // Reset the existing override configuration so it can be updated according to the latest
         // configuration.
-        clearSizeCompatMode();
+        mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
 
         if (!attachedToProcess()) {
             return;
@@ -10440,202 +10126,6 @@
         proto.end(token);
     }
 
-    /**
-     * The precomputed insets of the display in each rotation. This is used to make the size
-     * compatibility mode activity compute the configuration without relying on its current display.
-     */
-    static class CompatDisplayInsets {
-        /** The original rotation the compat insets were computed in. */
-        final @Rotation int mOriginalRotation;
-        /** The original requested orientation for the activity. */
-        final @Configuration.Orientation int mOriginalRequestedOrientation;
-        /** The container width on rotation 0. */
-        private final int mWidth;
-        /** The container height on rotation 0. */
-        private final int mHeight;
-        /** Whether the {@link Task} windowingMode represents a floating window*/
-        final boolean mIsFloating;
-        /**
-         * Whether is letterboxed because of fixed orientation or aspect ratio when
-         * the unresizable activity is first shown.
-         */
-        final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
-        /**
-         * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
-         * is used to compute the appBounds.
-         */
-        final Rect[] mNonDecorInsets = new Rect[4];
-        /**
-         * The stableInsets for each rotation. Includes the status bar inset and the
-         * nonDecorInsets. It is used to compute {@link Configuration#screenWidthDp} and
-         * {@link Configuration#screenHeightDp}.
-         */
-        final Rect[] mStableInsets = new Rect[4];
-
-        /** Constructs the environment to simulate the bounds behavior of the given container. */
-        CompatDisplayInsets(DisplayContent display, ActivityRecord container,
-                @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
-            mOriginalRotation = display.getRotation();
-            mIsFloating = container.getWindowConfiguration().tasksAreFloating();
-            mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
-            if (mIsFloating) {
-                final Rect containerBounds = container.getWindowConfiguration().getBounds();
-                mWidth = containerBounds.width();
-                mHeight = containerBounds.height();
-                // For apps in freeform, the task bounds are the parent bounds from the app's
-                // perspective. No insets because within a window.
-                final Rect emptyRect = new Rect();
-                for (int rotation = 0; rotation < 4; rotation++) {
-                    mNonDecorInsets[rotation] = emptyRect;
-                    mStableInsets[rotation] = emptyRect;
-                }
-                mIsInFixedOrientationOrAspectRatioLetterbox = false;
-                return;
-            }
-
-            final Task task = container.getTask();
-
-            mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
-
-            // Store the bounds of the Task for the non-resizable activity to use in size compat
-            // mode so that the activity will not be resized regardless the windowing mode it is
-            // currently in.
-            // When an activity needs to be letterboxed because of fixed orientation or aspect
-            // ratio, use resolved bounds instead of task bounds since the activity will be
-            // displayed within these even if it is in size compat mode.
-            final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
-                    ? letterboxedContainerBounds
-                    : task != null ? task.getBounds() : display.getBounds();
-            final boolean useActivityRotation = container.hasFixedRotationTransform()
-                    && mIsInFixedOrientationOrAspectRatioLetterbox;
-            final int filledContainerRotation = useActivityRotation
-                    ? container.getWindowConfiguration().getRotation()
-                    : display.getConfiguration().windowConfiguration.getRotation();
-            final Point dimensions = getRotationZeroDimensions(
-                    filledContainerBounds, filledContainerRotation);
-            mWidth = dimensions.x;
-            mHeight = dimensions.y;
-
-            // Bounds of the filled container if it doesn't fill the display.
-            final Rect unfilledContainerBounds =
-                    filledContainerBounds.equals(display.getBounds()) ? null : new Rect();
-            final DisplayPolicy policy = display.getDisplayPolicy();
-            for (int rotation = 0; rotation < 4; rotation++) {
-                mNonDecorInsets[rotation] = new Rect();
-                mStableInsets[rotation] = new Rect();
-                final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-                final int dw = rotated ? display.mBaseDisplayHeight : display.mBaseDisplayWidth;
-                final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
-                final DisplayPolicy.DecorInsets.Info decorInfo =
-                        policy.getDecorInsetsInfo(rotation, dw, dh);
-                if (useOverrideInsets) {
-                    mStableInsets[rotation].set(decorInfo.mOverrideConfigInsets);
-                    mNonDecorInsets[rotation].set(decorInfo.mOverrideNonDecorInsets);
-                } else {
-                    mStableInsets[rotation].set(decorInfo.mConfigInsets);
-                    mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
-                }
-
-                if (unfilledContainerBounds == null) {
-                    continue;
-                }
-                // The insets is based on the display, but the container may be smaller than the
-                // display, so update the insets to exclude parts that are not intersected with the
-                // container.
-                unfilledContainerBounds.set(filledContainerBounds);
-                display.rotateBounds(
-                        filledContainerRotation,
-                        rotation,
-                        unfilledContainerBounds);
-                updateInsetsForBounds(unfilledContainerBounds, dw, dh, mNonDecorInsets[rotation]);
-                updateInsetsForBounds(unfilledContainerBounds, dw, dh, mStableInsets[rotation]);
-            }
-        }
-
-        /**
-         * Gets the width and height of the {@code container} when it is not rotated, so that after
-         * the display is rotated, we can calculate the bounds by rotating the dimensions.
-         * @see #getBoundsByRotation
-         */
-        private static Point getRotationZeroDimensions(final Rect bounds, int rotation) {
-            final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-            final int width = bounds.width();
-            final int height = bounds.height();
-            return rotated ? new Point(height, width) : new Point(width, height);
-        }
-
-        /**
-         * Updates the display insets to exclude the parts that are not intersected with the given
-         * bounds.
-         */
-        private static void updateInsetsForBounds(Rect bounds, int displayWidth, int displayHeight,
-                Rect inset) {
-            inset.left = Math.max(0, inset.left - bounds.left);
-            inset.top = Math.max(0, inset.top - bounds.top);
-            inset.right = Math.max(0, bounds.right - displayWidth + inset.right);
-            inset.bottom = Math.max(0, bounds.bottom - displayHeight + inset.bottom);
-        }
-
-        void getBoundsByRotation(Rect outBounds, int rotation) {
-            final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-            final int dw = rotated ? mHeight : mWidth;
-            final int dh = rotated ? mWidth : mHeight;
-            outBounds.set(0, 0, dw, dh);
-        }
-
-        void getFrameByOrientation(Rect outBounds, int orientation) {
-            final int longSide = Math.max(mWidth, mHeight);
-            final int shortSide = Math.min(mWidth, mHeight);
-            final boolean isLandscape = orientation == ORIENTATION_LANDSCAPE;
-            outBounds.set(0, 0, isLandscape ? longSide : shortSide,
-                    isLandscape ? shortSide : longSide);
-        }
-
-        // TODO(b/267151420): Explore removing getContainerBounds() from CompatDisplayInsets.
-        /** Gets the horizontal centered container bounds for size compatibility mode. */
-        void getContainerBounds(Rect outAppBounds, Rect outBounds, int rotation, int orientation,
-                boolean orientationRequested, boolean isFixedToUserRotation) {
-            getFrameByOrientation(outBounds, orientation);
-            if (mIsFloating) {
-                outAppBounds.set(outBounds);
-                return;
-            }
-
-            getBoundsByRotation(outAppBounds, rotation);
-            final int dW = outAppBounds.width();
-            final int dH = outAppBounds.height();
-            final boolean isOrientationMismatched =
-                    ((outBounds.width() > outBounds.height()) != (dW > dH));
-
-            if (isOrientationMismatched && isFixedToUserRotation && orientationRequested) {
-                // The orientation is mismatched but the display cannot rotate. The bounds will fit
-                // to the short side of container.
-                if (orientation == ORIENTATION_LANDSCAPE) {
-                    outBounds.bottom = (int) ((float) dW * dW / dH);
-                    outBounds.right = dW;
-                } else {
-                    outBounds.bottom = dH;
-                    outBounds.right = (int) ((float) dH * dH / dW);
-                }
-                outBounds.offset(getCenterOffset(mWidth, outBounds.width()), 0 /* dy */);
-            }
-            outAppBounds.set(outBounds);
-
-            if (isOrientationMismatched) {
-                // One side of container is smaller than the requested size, then it will be scaled
-                // and the final position will be calculated according to the parent container and
-                // scale, so the original size shouldn't be shrunk by insets.
-                final Rect insets = mNonDecorInsets[rotation];
-                outBounds.offset(insets.left, insets.top);
-                outAppBounds.offset(insets.left, insets.top);
-            } else if (rotation != ROTATION_UNDEFINED) {
-                // Ensure the app bounds won't overlap with insets.
-                TaskFragment.intersectWithInsetsIfFits(outAppBounds, outBounds,
-                        mNonDecorInsets[rotation]);
-            }
-        }
-    }
-
     private static class AppSaturationInfo {
         float[] mMatrix = new float[9];
         float[] mTranslation = new float[3];
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 1660ca9..35ec5ad 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -424,13 +424,19 @@
                 Intent intent = intents[i];
                 NeededUriGrants intentGrants = null;
 
-                intent.prepareToEnterSystemServer();
+                // Refuse possible leaked file descriptors.
+                if (intent.hasFileDescriptors()) {
+                    throw new IllegalArgumentException("File descriptors passed in Intent");
+                }
 
                 // Get the flag earlier because the intent may be modified in resolveActivity below.
                 final boolean componentSpecified = intent.getComponent() != null;
                 // Don't modify the client's object!
                 intent = new Intent(intent);
 
+                // Remove existing mismatch flag so it can be properly updated later
+                intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+
                 // Collect information about the target of the Intent.
                 ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i],
                         0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 6479111..1822a80 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -721,7 +721,13 @@
             onExecutionStarted();
 
             if (mRequest.intent != null) {
-                mRequest.intent.prepareToEnterSystemServer();
+                // Refuse possible leaked file descriptors
+                if (mRequest.intent.hasFileDescriptors()) {
+                    throw new IllegalArgumentException("File descriptors passed in Intent");
+                }
+
+                // Remove existing mismatch flag so it can be properly updated later
+                mRequest.intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
             }
 
             final LaunchingState launchingState;
@@ -1945,7 +1951,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 2f74a9d..7102167 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -438,13 +438,10 @@
 
     /** It is set from keyguard-going-away to set-keyguard-shown. */
     static final int DEMOTE_TOP_REASON_DURING_UNLOCKING = 1;
-    /** It is set if legacy recents animation is running. */
-    static final int DEMOTE_TOP_REASON_ANIMATING_RECENTS = 1 << 1;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
             DEMOTE_TOP_REASON_DURING_UNLOCKING,
-            DEMOTE_TOP_REASON_ANIMATING_RECENTS,
     })
     @interface DemoteTopReason {}
 
@@ -798,6 +795,7 @@
     WindowOrganizerController mWindowOrganizerController;
     TaskOrganizerController mTaskOrganizerController;
     TaskFragmentOrganizerController mTaskFragmentOrganizerController;
+    ActionChain.Tracker mChainTracker;
 
     @Nullable
     private BackgroundActivityStartCallback mBackgroundActivityStartCallback;
@@ -872,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;
@@ -1318,7 +1317,12 @@
             String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle bOptions) {
         enforceNotIsolatedCaller("startActivityIntentSender");
         if (fillInIntent != null) {
-            fillInIntent.prepareToEnterSystemServer();
+            // Refuse possible leaked file descriptors
+            if (fillInIntent.hasFileDescriptors()) {
+                throw new IllegalArgumentException("File descriptors passed in Intent");
+            }
+            // Remove existing mismatch flag so it can be properly updated later
+            fillInIntent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
         }
 
         if (!(target instanceof PendingIntentRecord)) {
@@ -1344,10 +1348,10 @@
     @Override
     public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent,
             Bundle bOptions) {
-        if (intent != null) {
-            intent.prepareToEnterSystemServer();
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
         }
-
         SafeActivityOptions options = SafeActivityOptions.fromBundle(bOptions);
 
         synchronized (mGlobalLock) {
@@ -1362,6 +1366,8 @@
                 return false;
             }
             intent = new Intent(intent);
+            // Remove existing mismatch flag so it can be properly updated later
+            intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
             // The caller is not allowed to change the data.
             intent.setDataAndType(r.intent.getData(), r.intent.getType());
             // And we are resetting to find the next component...
@@ -1777,19 +1783,15 @@
     @Override
     public void preloadRecentsActivity(Intent intent) {
         enforceTaskPermission("preloadRecentsActivity()");
-        final int callingPid = Binder.getCallingPid();
-        final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
                 final ComponentName recentsComponent = mRecentTasks.getRecentsComponent();
                 final String recentsFeatureId = mRecentTasks.getRecentsComponentFeatureId();
                 final int recentsUid = mRecentTasks.getRecentsComponentUid();
-                final WindowProcessController caller = getProcessController(callingPid, callingUid);
-
                 final RecentsAnimation anim = new RecentsAnimation(this, mTaskSupervisor,
-                        getActivityStartController(), mWindowManager, intent, recentsComponent,
-                        recentsFeatureId, recentsUid, caller);
+                        getActivityStartController(), intent, recentsComponent,
+                        recentsFeatureId, recentsUid);
                 anim.preloadRecentsActivity();
             }
         } finally {
@@ -3624,6 +3626,11 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
+                boolean isPowerModePreApplied = false;
+                if (mPowerModeReasons == 0) {
+                    startPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+                    isPowerModePreApplied = true;
+                }
                 // Keyguard asked us to clear the home task snapshot before going away, so do that.
                 if ((flags & KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) {
                     mActivityClientController.invalidateHomeTaskSnapshot(null /* token */);
@@ -3632,9 +3639,19 @@
                     mDemoteTopAppReasons |= DEMOTE_TOP_REASON_DURING_UNLOCKING;
                 }
 
-                mRootWindowContainer.forAllDisplays(displayContent -> {
-                    mKeyguardController.keyguardGoingAway(displayContent.getDisplayId(), flags);
-                });
+                boolean foundResumed = false;
+                for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
+                    final DisplayContent dc = mRootWindowContainer.getChildAt(i);
+                    final boolean wasNoResumed = dc.mFocusedApp == null
+                            || !dc.mFocusedApp.isState(RESUMED);
+                    mKeyguardController.keyguardGoingAway(dc.mDisplayId, flags);
+                    if (wasNoResumed && dc.mFocusedApp != null && dc.mFocusedApp.isState(RESUMED)) {
+                        foundResumed = true;
+                    }
+                }
+                if (isPowerModePreApplied && !foundResumed) {
+                    endPowerMode(POWER_MODE_REASON_START_ACTIVITY);
+                }
             }
             WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
             if (wallpaperManagerInternal != null) {
@@ -3786,9 +3803,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 */,
@@ -4747,6 +4777,10 @@
         }
     }
 
+    boolean mayBeLaunchingApp() {
+        return (mPowerModeReasons & POWER_MODE_REASON_START_ACTIVITY) != 0;
+    }
+
     void startPowerMode(@PowerModeReason int reason) {
         final int prevReasons = mPowerModeReasons;
         mPowerModeReasons |= reason;
@@ -7447,9 +7481,7 @@
                     FEATURE_LEANBACK);
             final boolean isArc = arcFeature != null && arcFeature.version >= 0;
             final boolean isTv = tvFeature != null && tvFeature.version >= 0;
-            sIsPip2ExperimentEnabled = SystemProperties.getBoolean(
-                    "persist.wm_shell.pip2", false)
-                    || (Flags.enablePip2Implementation() && !isArc && !isTv);
+            sIsPip2ExperimentEnabled = Flags.enablePip2() && !isArc && !isTv;
         }
         return sIsPip2ExperimentEnabled;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 1446c35..9a3ad2d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -107,7 +107,6 @@
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.StopActivityItem;
-import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -158,6 +157,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.HostingRecord;
 import com.android.server.am.UserState;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.pm.SaferIntentUtils;
 import com.android.server.utils.Slogf;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
@@ -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;
@@ -279,7 +285,7 @@
     private WindowManagerService mWindowManager;
 
     private AppOpsManager mAppOpsManager;
-    private VirtualDeviceManager mVirtualDeviceManager;
+    private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
 
     /** Common synchronization logic used to save things to disks. */
     PersisterQueue mPersisterQueue;
@@ -1292,16 +1298,24 @@
         if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY)  {
             return Context.DEVICE_ID_DEFAULT;
         }
-        if (mVirtualDeviceManager == null) {
+        if (mVirtualDeviceManagerInternal == null) {
             if (mService.mHasCompanionDeviceSetupFeature) {
-                mVirtualDeviceManager =
-                        mService.mContext.getSystemService(VirtualDeviceManager.class);
+                mVirtualDeviceManagerInternal =
+                        LocalServices.getService(VirtualDeviceManagerInternal.class);
             }
-            if (mVirtualDeviceManager == null) {
+            if (mVirtualDeviceManagerInternal == null) {
                 return Context.DEVICE_ID_DEFAULT;
             }
         }
-        return mVirtualDeviceManager.getDeviceIdForDisplayId(displayId);
+        return mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(displayId);
+    }
+
+    boolean isDeviceOwnerUid(int displayId, int callingUid) {
+        final int deviceId = getDeviceIdForDisplayId(displayId);
+        if (deviceId == Context.DEVICE_ID_DEFAULT || deviceId == Context.DEVICE_ID_INVALID) {
+            return false;
+        }
+        return mVirtualDeviceManagerInternal.getDeviceOwnerUid(deviceId) == callingUid;
     }
 
     private AppOpsManager getAppOpsManager() {
@@ -2252,7 +2266,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/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index cd795ae..f245efd 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -220,7 +220,7 @@
     float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) {
         return shouldUseSplitScreenAspectRatio(parentConfiguration)
                 ? getSplitScreenAspectRatio()
-                : mActivityRecord.shouldCreateCompatDisplayInsets()
+                : mActivityRecord.shouldCreateAppCompatDisplayInsets()
                         ? getDefaultMinAspectRatioForUnresizableApps()
                         : getDefaultMinAspectRatio();
     }
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index aeaaffd..d8abf69 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -217,7 +217,7 @@
      */
     boolean isCameraCompatSplitScreenAspectRatioAllowed() {
         return mAppCompatConfiguration.isCameraCompatSplitScreenAspectRatioEnabled()
-                && !mActivityRecord.shouldCreateCompatDisplayInsets();
+                && !mActivityRecord.shouldCreateAppCompatDisplayInsets();
     }
 
     @FreeformCameraCompatMode
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 3c3b773..173362c 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -46,6 +46,8 @@
     private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
     @NonNull
     private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
+    @NonNull
+    private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
 
     AppCompatController(@NonNull WindowManagerService wmService,
                         @NonNull ActivityRecord activityRecord) {
@@ -67,6 +69,8 @@
                 wmService.mAppCompatConfiguration);
         mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord,
                 mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
+        mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
+                mAppCompatOverrides);
     }
 
     @NonNull
@@ -152,9 +156,15 @@
         return mAppCompatOverrides.getAppCompatLetterboxOverrides();
     }
 
+    @NonNull
+    AppCompatSizeCompatModePolicy getAppCompatSizeCompatModePolicy() {
+        return mAppCompatSizeCompatModePolicy;
+    }
+
     void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         getTransparentPolicy().dump(pw, prefix);
         getAppCompatLetterboxPolicy().dump(pw, prefix);
+        getAppCompatSizeCompatModePolicy().dump(pw, prefix);
     }
 
 }
diff --git a/services/core/java/com/android/server/wm/AppCompatDisplayInsets.java b/services/core/java/com/android/server/wm/AppCompatDisplayInsets.java
new file mode 100644
index 0000000..743bfb9
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatDisplayInsets.java
@@ -0,0 +1,234 @@
+/*
+ * 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 static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Surface;
+
+/**
+ * The precomputed insets of the display in each rotation. This is used to make the size
+ * compatibility mode activity compute the configuration without relying on its current display.
+ */
+class AppCompatDisplayInsets {
+    /** The original rotation the compat insets were computed in. */
+    final @Surface.Rotation int mOriginalRotation;
+    /** The original requested orientation for the activity. */
+    final @Configuration.Orientation int mOriginalRequestedOrientation;
+    /** The container width on rotation 0. */
+    private final int mWidth;
+    /** The container height on rotation 0. */
+    private final int mHeight;
+    /** Whether the {@link Task} windowingMode represents a floating window*/
+    final boolean mIsFloating;
+    /**
+     * Whether is letterboxed because of fixed orientation or aspect ratio when
+     * the unresizable activity is first shown.
+     */
+    final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
+    /**
+     * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
+     * is used to compute the appBounds.
+     */
+    final Rect[] mNonDecorInsets = new Rect[4];
+    /**
+     * The stableInsets for each rotation. Includes the status bar inset and the
+     * nonDecorInsets. It is used to compute {@link Configuration#screenWidthDp} and
+     * {@link Configuration#screenHeightDp}.
+     */
+    final Rect[] mStableInsets = new Rect[4];
+
+    /** Constructs the environment to simulate the bounds behavior of the given container. */
+    AppCompatDisplayInsets(@NonNull DisplayContent display, @NonNull ActivityRecord container,
+            @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
+        mOriginalRotation = display.getRotation();
+        mIsFloating = container.getWindowConfiguration().tasksAreFloating();
+        mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
+        if (mIsFloating) {
+            final Rect containerBounds = container.getWindowConfiguration().getBounds();
+            mWidth = containerBounds.width();
+            mHeight = containerBounds.height();
+            // For apps in freeform, the task bounds are the parent bounds from the app's
+            // perspective. No insets because within a window.
+            final Rect emptyRect = new Rect();
+            for (int rotation = 0; rotation < 4; rotation++) {
+                mNonDecorInsets[rotation] = emptyRect;
+                mStableInsets[rotation] = emptyRect;
+            }
+            mIsInFixedOrientationOrAspectRatioLetterbox = false;
+            return;
+        }
+
+        final Task task = container.getTask();
+
+        mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
+
+        // Store the bounds of the Task for the non-resizable activity to use in size compat
+        // mode so that the activity will not be resized regardless the windowing mode it is
+        // currently in.
+        // When an activity needs to be letterboxed because of fixed orientation or aspect
+        // ratio, use resolved bounds instead of task bounds since the activity will be
+        // displayed within these even if it is in size compat mode.
+        final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
+                ? letterboxedContainerBounds
+                : task != null ? task.getBounds() : display.getBounds();
+        final boolean useActivityRotation = container.hasFixedRotationTransform()
+                && mIsInFixedOrientationOrAspectRatioLetterbox;
+        final int filledContainerRotation = useActivityRotation
+                ? container.getWindowConfiguration().getRotation()
+                : display.getConfiguration().windowConfiguration.getRotation();
+        final Point dimensions = getRotationZeroDimensions(
+                filledContainerBounds, filledContainerRotation);
+        mWidth = dimensions.x;
+        mHeight = dimensions.y;
+
+        // Bounds of the filled container if it doesn't fill the display.
+        final Rect unfilledContainerBounds =
+                filledContainerBounds.equals(display.getBounds()) ? null : new Rect();
+        final DisplayPolicy policy = display.getDisplayPolicy();
+        for (int rotation = 0; rotation < 4; rotation++) {
+            mNonDecorInsets[rotation] = new Rect();
+            mStableInsets[rotation] = new Rect();
+            final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+            final int dw = rotated ? display.mBaseDisplayHeight : display.mBaseDisplayWidth;
+            final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
+            final DisplayPolicy.DecorInsets.Info decorInfo =
+                    policy.getDecorInsetsInfo(rotation, dw, dh);
+            if (useOverrideInsets) {
+                mStableInsets[rotation].set(decorInfo.mOverrideConfigInsets);
+                mNonDecorInsets[rotation].set(decorInfo.mOverrideNonDecorInsets);
+            } else {
+                mStableInsets[rotation].set(decorInfo.mConfigInsets);
+                mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
+            }
+
+            if (unfilledContainerBounds == null) {
+                continue;
+            }
+            // The insets is based on the display, but the container may be smaller than the
+            // display, so update the insets to exclude parts that are not intersected with the
+            // container.
+            unfilledContainerBounds.set(filledContainerBounds);
+            display.rotateBounds(
+                    filledContainerRotation,
+                    rotation,
+                    unfilledContainerBounds);
+            updateInsetsForBounds(unfilledContainerBounds, dw, dh, mNonDecorInsets[rotation]);
+            updateInsetsForBounds(unfilledContainerBounds, dw, dh, mStableInsets[rotation]);
+        }
+    }
+
+    /**
+     * Gets the width and height of the {@code container} when it is not rotated, so that after
+     * the display is rotated, we can calculate the bounds by rotating the dimensions.
+     * @see #getBoundsByRotation
+     */
+    @NonNull
+    private static Point getRotationZeroDimensions(final @NonNull Rect bounds,
+            @Surface.Rotation int rotation) {
+        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+        final int width = bounds.width();
+        final int height = bounds.height();
+        return rotated ? new Point(height, width) : new Point(width, height);
+    }
+
+    /**
+     * Updates the display insets to exclude the parts that are not intersected with the given
+     * bounds.
+     */
+    private static void updateInsetsForBounds(@NonNull Rect bounds, int displayWidth,
+            int displayHeight, @NonNull Rect inset) {
+        inset.left = Math.max(0, inset.left - bounds.left);
+        inset.top = Math.max(0, inset.top - bounds.top);
+        inset.right = Math.max(0, bounds.right - displayWidth + inset.right);
+        inset.bottom = Math.max(0, bounds.bottom - displayHeight + inset.bottom);
+    }
+
+    void getBoundsByRotation(@NonNull Rect outBounds, @Surface.Rotation int rotation) {
+        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+        final int dw = rotated ? mHeight : mWidth;
+        final int dh = rotated ? mWidth : mHeight;
+        outBounds.set(0, 0, dw, dh);
+    }
+
+    void getFrameByOrientation(@NonNull Rect outBounds,
+            @Configuration.Orientation int orientation) {
+        final int longSide = Math.max(mWidth, mHeight);
+        final int shortSide = Math.min(mWidth, mHeight);
+        final boolean isLandscape = orientation == ORIENTATION_LANDSCAPE;
+        outBounds.set(0, 0, isLandscape ? longSide : shortSide,
+                isLandscape ? shortSide : longSide);
+    }
+
+    /** Gets the horizontal centered container bounds for size compatibility mode. */
+    void getContainerBounds(@NonNull Rect outAppBounds, @NonNull Rect outBounds,
+            @Surface.Rotation int rotation, @Configuration.Orientation int orientation,
+            boolean orientationRequested, boolean isFixedToUserRotation) {
+        getFrameByOrientation(outBounds, orientation);
+        if (mIsFloating) {
+            outAppBounds.set(outBounds);
+            return;
+        }
+
+        getBoundsByRotation(outAppBounds, rotation);
+        final int dW = outAppBounds.width();
+        final int dH = outAppBounds.height();
+        final boolean isOrientationMismatched =
+                ((outBounds.width() > outBounds.height()) != (dW > dH));
+
+        if (isOrientationMismatched && isFixedToUserRotation && orientationRequested) {
+            // The orientation is mismatched but the display cannot rotate. The bounds will fit
+            // to the short side of container.
+            if (orientation == ORIENTATION_LANDSCAPE) {
+                outBounds.bottom = (int) ((float) dW * dW / dH);
+                outBounds.right = dW;
+            } else {
+                outBounds.bottom = dH;
+                outBounds.right = (int) ((float) dH * dH / dW);
+            }
+            outBounds.offset(getCenterOffset(mWidth, outBounds.width()), 0 /* dy */);
+        }
+        outAppBounds.set(outBounds);
+
+        if (isOrientationMismatched) {
+            // One side of container is smaller than the requested size, then it will be scaled
+            // and the final position will be calculated according to the parent container and
+            // scale, so the original size shouldn't be shrunk by insets.
+            final Rect insets = mNonDecorInsets[rotation];
+            outBounds.offset(insets.left, insets.top);
+            outAppBounds.offset(insets.left, insets.top);
+        } else if (rotation != ROTATION_UNDEFINED) {
+            // Ensure the app bounds won't overlap with insets.
+            TaskFragment.intersectWithInsetsIfFits(outAppBounds, outBounds,
+                    mNonDecorInsets[rotation]);
+        }
+    }
+
+    /** @return The horizontal / vertical offset of putting the content in the center of viewport.*/
+    private static int getCenterOffset(int viewportDim, int contentDim) {
+        return (int) ((viewportDim - contentDim + 1) * 0.5f);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
new file mode 100644
index 0000000..3be266e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -0,0 +1,439 @@
+/*
+ * 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 static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+
+import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import com.android.window.flags.Flags;
+
+import java.io.PrintWriter;
+import java.util.function.DoubleSupplier;
+
+/**
+ * Encapsulate logic related to the SizeCompatMode.
+ */
+class AppCompatSizeCompatModePolicy {
+
+    @NonNull
+    private final ActivityRecord mActivityRecord;
+    @NonNull
+    private final AppCompatOverrides mAppCompatOverrides;
+
+    // Whether this activity is in size compatibility mode because its bounds don't fit in parent
+    // naturally.
+    private boolean mInSizeCompatModeForBounds = false;
+    /**
+     * The scale to fit at least one side of the activity to its parent. If the activity uses
+     * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5.
+     */
+    private float mSizeCompatScale = 1f;
+
+    /**
+     * The bounds in global coordinates for activity in size compatibility mode.
+     * @see #hasSizeCompatBounds()
+     */
+    private Rect mSizeCompatBounds;
+
+    /**
+     * The precomputed display insets for resolving configuration. It will be non-null if
+     * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}.
+     */
+    @Nullable
+    private AppCompatDisplayInsets mAppCompatDisplayInsets;
+
+    AppCompatSizeCompatModePolicy(@NonNull ActivityRecord activityRecord,
+            @NonNull AppCompatOverrides appCompatOverrides) {
+        mActivityRecord = activityRecord;
+        mAppCompatOverrides = appCompatOverrides;
+    }
+
+    boolean isInSizeCompatModeForBounds() {
+        return mInSizeCompatModeForBounds;
+    }
+
+    void setInSizeCompatModeForBounds(boolean inSizeCompatModeForBounds) {
+        mInSizeCompatModeForBounds = inSizeCompatModeForBounds;
+    }
+
+    boolean hasSizeCompatBounds() {
+        return mSizeCompatBounds != null;
+    }
+
+    /**
+     * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without
+     * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
+     */
+    boolean hasAppCompatDisplayInsetsWithoutInheritance() {
+        return mAppCompatDisplayInsets != null;
+    }
+
+    @Nullable
+    AppCompatDisplayInsets getAppCompatDisplayInsets() {
+        final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController
+                .getTransparentPolicy();
+        if (transparentPolicy.isRunning()) {
+            return transparentPolicy.getInheritedAppCompatDisplayInsets();
+        }
+        return mAppCompatDisplayInsets;
+    }
+
+    float getCompatScaleIfAvailable(@NonNull DoubleSupplier scaleWhenNotAvailable) {
+        return hasSizeCompatBounds() ? mSizeCompatScale
+                : (float) scaleWhenNotAvailable.getAsDouble();
+    }
+
+    @NonNull
+    Rect getAppSizeCompatBoundsIfAvailable(@NonNull Rect boundsWhenNotAvailable) {
+        return hasSizeCompatBounds() ? mSizeCompatBounds : boundsWhenNotAvailable;
+    }
+
+    @NonNull
+    Rect replaceResolvedBoundsIfNeeded(@NonNull Rect resolvedBounds) {
+        return hasSizeCompatBounds() ? mSizeCompatBounds : resolvedBounds;
+    }
+
+    boolean applyOffsetIfNeeded(@NonNull Rect resolvedBounds,
+            @NonNull Configuration resolvedConfig, int offsetX, int offsetY) {
+        if (hasSizeCompatBounds()) {
+            mSizeCompatBounds.offset(offsetX , offsetY);
+            final int dy = mSizeCompatBounds.top - resolvedBounds.top;
+            final int dx = mSizeCompatBounds.left - resolvedBounds.left;
+            AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
+            return true;
+        }
+        return false;
+    }
+
+    void alignToTopIfNeeded(@NonNull Rect parentBounds) {
+        if (hasSizeCompatBounds()) {
+            mSizeCompatBounds.top = parentBounds.top;
+        }
+    }
+
+    void applySizeCompatScaleIfNeeded(@NonNull Rect resolvedBounds,
+            @NonNull Configuration resolvedConfig) {
+        if (mSizeCompatScale != 1f) {
+            final int screenPosX = resolvedBounds.left;
+            final int screenPosY = resolvedBounds.top;
+            final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX;
+            final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY;
+            AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
+        }
+    }
+
+    void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds) {
+        mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy()
+                .findOpaqueNotFinishingActivityBelow()
+                .map(activityRecord -> mSizeCompatScale)
+                .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds));
+    }
+
+    void clearSizeCompatModeAttributes() {
+        mInSizeCompatModeForBounds = false;
+        final float lastSizeCompatScale = mSizeCompatScale;
+        mSizeCompatScale = 1f;
+        if (mSizeCompatScale != lastSizeCompatScale) {
+            mActivityRecord.forAllWindows(WindowState::updateGlobalScale,
+                    false /* traverseTopToBottom */);
+        }
+        mSizeCompatBounds = null;
+        mAppCompatDisplayInsets = null;
+        mActivityRecord.mAppCompatController.getTransparentPolicy()
+                .clearInheritedAppCompatDisplayInsets();
+    }
+
+    void clearSizeCompatMode() {
+        clearSizeCompatModeAttributes();
+        // Clear config override in #updateAppCompatDisplayInsets().
+        final int activityType = mActivityRecord.getActivityType();
+        final Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration();
+        overrideConfig.unset();
+        // Keep the activity type which was set when attaching to a task to prevent leaving it
+        // undefined.
+        overrideConfig.windowConfiguration.setActivityType(activityType);
+        mActivityRecord.onRequestedOverrideConfigurationChanged(overrideConfig);
+    }
+
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        if (mSizeCompatScale != 1f || hasSizeCompatBounds()) {
+            pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds="
+                    + mSizeCompatBounds);
+        }
+    }
+
+    /**
+     * Resolves consistent screen configuration for orientation and rotation changes without
+     * inheriting the parent bounds.
+     */
+    void resolveSizeCompatModeConfiguration(@NonNull Configuration newParentConfiguration,
+            @NonNull AppCompatDisplayInsets appCompatDisplayInsets, @NonNull Rect tmpBounds) {
+        final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration();
+        final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+
+        // When an activity needs to be letterboxed because of fixed orientation, use fixed
+        // orientation bounds (stored in resolved bounds) instead of parent bounds since the
+        // activity will be displayed within them even if it is in size compat mode. They should be
+        // saved here before resolved bounds are overridden below.
+        final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController
+                .getAppCompatAspectRatioPolicy();
+        final boolean useResolvedBounds = Flags.immersiveAppRepositioning()
+                ? aspectRatioPolicy.isAspectRatioApplied()
+                : aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio();
+        final Rect containerBounds = useResolvedBounds
+                ? new Rect(resolvedBounds)
+                : newParentConfiguration.windowConfiguration.getBounds();
+        final Rect containerAppBounds = useResolvedBounds
+                ? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
+                : mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride;
+
+        final int requestedOrientation = mActivityRecord.getRequestedConfigurationOrientation();
+        final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
+        final int parentOrientation = mActivityRecord.mResolveConfigHint.mUseOverrideInsetsForConfig
+                ? mActivityRecord.mResolveConfigHint.mTmpOverrideConfigOrientation
+                : newParentConfiguration.orientation;
+        final int orientation = orientationRequested
+                ? requestedOrientation
+                // We should use the original orientation of the activity when possible to avoid
+                // forcing the activity in the opposite orientation.
+                : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+                        ? appCompatDisplayInsets.mOriginalRequestedOrientation
+                        : parentOrientation;
+        int rotation = newParentConfiguration.windowConfiguration.getRotation();
+        final boolean isFixedToUserRotation = mActivityRecord.mDisplayContent == null
+                || mActivityRecord.mDisplayContent.getDisplayRotation().isFixedToUserRotation();
+        if (!isFixedToUserRotation && !appCompatDisplayInsets.mIsFloating) {
+            // Use parent rotation because the original display can be rotated.
+            resolvedConfig.windowConfiguration.setRotation(rotation);
+        } else {
+            final int overrideRotation = resolvedConfig.windowConfiguration.getRotation();
+            if (overrideRotation != ROTATION_UNDEFINED) {
+                rotation = overrideRotation;
+            }
+        }
+
+        // Use compat insets to lock width and height. We should not use the parent width and height
+        // because apps in compat mode should have a constant width and height. The compat insets
+        // are locked when the app is first launched and are never changed after that, so we can
+        // rely on them to contain the original and unchanging width and height of the app.
+        final Rect containingAppBounds = new Rect();
+        final Rect containingBounds = tmpBounds;
+        appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
+                orientation, orientationRequested, isFixedToUserRotation);
+        resolvedBounds.set(containingBounds);
+        // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
+        if (!appCompatDisplayInsets.mIsFloating) {
+            mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+                    .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
+                            containingBounds);
+        }
+
+        // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
+        // are calculated in compat container space. The actual position on screen will be applied
+        // later, so the calculation is simpler that doesn't need to involve offset from parent.
+        mActivityRecord.mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets;
+        mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+        // Use current screen layout as source because the size of app is independent to parent.
+        resolvedConfig.screenLayout = ActivityRecord.computeScreenLayout(
+                mActivityRecord.getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
+                resolvedConfig.screenHeightDp);
+
+        // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside
+        // the parent bounds appropriately.
+        if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) {
+            resolvedConfig.orientation = parentOrientation;
+        }
+
+        // Below figure is an example that puts an activity which was launched in a larger container
+        // into a smaller container.
+        //   The outermost rectangle is the real display bounds.
+        //   "@" is the container app bounds (parent bounds or fixed orientation bounds)
+        //   "#" is the {@code resolvedBounds} that applies to application.
+        //   "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
+        // ------------------------------
+        // |                            |
+        // |    @@@@*********@@@@###    |
+        // |    @   *       *   @  #    |
+        // |    @   *       *   @  #    |
+        // |    @   *       *   @  #    |
+        // |    @@@@*********@@@@  #    |
+        // ---------#--------------#-----
+        //          #              #
+        //          ################
+        // The application is still layouted in "#" since it was launched, and it will be visually
+        // scaled and positioned to "*".
+
+        final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
+        // Calculates the scale the size compatibility bounds into the region which is available
+        // to application.
+        final float lastSizeCompatScale = mSizeCompatScale;
+        updateSizeCompatScale(resolvedAppBounds, containerAppBounds);
+
+        final int containerTopInset = containerAppBounds.top - containerBounds.top;
+        final boolean topNotAligned =
+                containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
+        if (mSizeCompatScale != 1f || topNotAligned) {
+            if (mSizeCompatBounds == null) {
+                mSizeCompatBounds = new Rect();
+            }
+            mSizeCompatBounds.set(resolvedAppBounds);
+            mSizeCompatBounds.offsetTo(0, 0);
+            mSizeCompatBounds.scale(mSizeCompatScale);
+            // The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
+            mSizeCompatBounds.bottom += containerTopInset;
+        } else {
+            mSizeCompatBounds = null;
+        }
+        if (mSizeCompatScale != lastSizeCompatScale) {
+            mActivityRecord.forAllWindows(WindowState::updateGlobalScale,
+                    false /* traverseTopToBottom */);
+        }
+
+        // The position will be later adjusted in updateResolvedBoundsPosition.
+        // Above coordinates are in "@" space, now place "*" and "#" to screen space.
+        final boolean fillContainer = resolvedBounds.equals(containingBounds);
+        final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
+        final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
+
+        if (screenPosX != 0 || screenPosY != 0) {
+            if (hasSizeCompatBounds()) {
+                mSizeCompatBounds.offset(screenPosX, screenPosY);
+            }
+            // Add the global coordinates and remove the local coordinates.
+            final int dx = screenPosX - resolvedBounds.left;
+            final int dy = screenPosY - resolvedBounds.top;
+            AppCompatUtils.offsetBounds(resolvedConfig, dx, dy);
+        }
+
+        mInSizeCompatModeForBounds = isInSizeCompatModeForBounds(resolvedAppBounds,
+                containerAppBounds);
+    }
+
+    // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
+    void updateAppCompatDisplayInsets() {
+        if (getAppCompatDisplayInsets() != null
+                || !mActivityRecord.shouldCreateAppCompatDisplayInsets()) {
+            // The override configuration is set only once in size compatibility mode.
+            return;
+        }
+
+        Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration();
+        final Configuration fullConfig = mActivityRecord.getConfiguration();
+
+        // Ensure the screen related fields are set. It is used to prevent activity relaunch
+        // when moving between displays. For screenWidthDp and screenWidthDp, because they
+        // are relative to bounds and density, they will be calculated in
+        // {@link Task#computeConfigResourceOverrides} and the result will also be
+        // relatively fixed.
+        overrideConfig.colorMode = fullConfig.colorMode;
+        overrideConfig.densityDpi = fullConfig.densityDpi;
+        // The smallest screen width is the short side of screen bounds. Because the bounds
+        // and density won't be changed, smallestScreenWidthDp is also fixed.
+        overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
+        if (ActivityInfo.isFixedOrientation(mActivityRecord.getOverrideOrientation())) {
+            // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
+            // apply runtime rotation changes.
+            overrideConfig.windowConfiguration.setRotation(
+                    fullConfig.windowConfiguration.getRotation());
+        }
+
+        final Rect letterboxedContainerBounds = mActivityRecord.mAppCompatController
+                .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
+
+        // The role of AppCompatDisplayInsets is like the override bounds.
+        mAppCompatDisplayInsets =
+                new AppCompatDisplayInsets(mActivityRecord.mDisplayContent, mActivityRecord,
+                        letterboxedContainerBounds, mActivityRecord.mResolveConfigHint
+                            .mUseOverrideInsetsForConfig);
+    }
+
+
+    private boolean isInSizeCompatModeForBounds(final @NonNull Rect appBounds,
+            final @NonNull Rect containerBounds) {
+        if (mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()) {
+            // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
+            // is letterboxed.
+            return false;
+        }
+        final int appWidth = appBounds.width();
+        final int appHeight = appBounds.height();
+        final int containerAppWidth = containerBounds.width();
+        final int containerAppHeight = containerBounds.height();
+
+        if (containerAppWidth == appWidth && containerAppHeight == appHeight) {
+            // Matched the container bounds.
+            return false;
+        }
+        if (containerAppWidth > appWidth && containerAppHeight > appHeight) {
+            // Both sides are smaller than the container.
+            return true;
+        }
+        if (containerAppWidth < appWidth || containerAppHeight < appHeight) {
+            // One side is larger than the container.
+            return true;
+        }
+
+        // The rest of the condition is that only one side is smaller than the container, but it
+        // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
+        final float maxAspectRatio = mActivityRecord.getMaxAspectRatio();
+        if (maxAspectRatio > 0) {
+            final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
+                    / Math.min(appWidth, appHeight);
+            if (aspectRatio >= maxAspectRatio) {
+                // The current size has reached the max aspect ratio.
+                return false;
+            }
+        }
+        final float minAspectRatio = mActivityRecord.getMinAspectRatio();
+        if (minAspectRatio > 0) {
+            // The activity should have at least the min aspect ratio, so this checks if the
+            // container still has available space to provide larger aspect ratio.
+            final float containerAspectRatio =
+                    (0.5f + Math.max(containerAppWidth, containerAppHeight))
+                            / Math.min(containerAppWidth, containerAppHeight);
+            if (containerAspectRatio <= minAspectRatio) {
+                // The long side has reached the parent.
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds,
+            @NonNull Rect containerAppBounds) {
+        final int contentW = resolvedAppBounds.width();
+        final int contentH = resolvedAppBounds.height();
+        final int viewportW = containerAppBounds.width();
+        final int viewportH = containerAppBounds.height();
+        // Allow an application to be up-scaled if its window is smaller than its
+        // original container or if it's a freeform window in desktop mode.
+        boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH)
+                || (canEnterDesktopMode(mActivityRecord.mAtmService.mContext)
+                    && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FREEFORM);
+        return shouldAllowUpscaling ? Math.min(
+                (float) viewportW / contentW, (float) viewportH / contentH) : 1f;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index e3ff851..69421d0 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -251,6 +251,11 @@
         }
     }
 
+    static void offsetBounds(@NonNull Configuration inOutConfig, int offsetX, int offsetY) {
+        inOutConfig.windowConfiguration.getBounds().offset(offsetX, offsetY);
+        inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY);
+    }
+
     private static void clearAppCompatTaskInfo(@NonNull AppCompatTaskInfo info) {
         info.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
         info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index bc7e84a..90d33fb 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -134,8 +134,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.util.DumpUtils.Dump;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
@@ -402,8 +402,7 @@
             mRemoteAnimationController.goodToGo(transit);
         } else if ((isTaskOpenTransitOld(transit) || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
                 && topOpeningAnim != null) {
-            if (mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
-                    && mService.getRecentsAnimationController() == null) {
+            if (mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()) {
                 final NavBarFadeAnimationController controller =
                         new NavBarFadeAnimationController(mDisplayContent);
                 // For remote animation case, the nav bar fades out and in is controlled by the
@@ -471,11 +470,9 @@
     }
 
     private boolean needsBoosting() {
-        final boolean recentsAnimRunning = mService.getRecentsAnimationController() != null;
         return !mNextAppTransitionRequests.isEmpty()
                 || mAppTransitionState == APP_STATE_READY
-                || mAppTransitionState == APP_STATE_RUNNING
-                || recentsAnimRunning;
+                || mAppTransitionState == APP_STATE_RUNNING;
     }
 
     void registerListenerLocked(AppTransitionListener listener) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 5a0cbf3..ab02d49 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -68,7 +68,6 @@
 import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
 import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -98,6 +97,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -173,41 +173,6 @@
                 || !transitionGoodToGoForTaskFragments()) {
             return;
         }
-        final boolean isRecentsInOpening = mDisplayContent.mOpeningApps.stream().anyMatch(
-                ConfigurationContainer::isActivityTypeRecents);
-        // In order to avoid visual clutter caused by a conflict between app transition
-        // animation and recents animation, app transition is delayed until recents finishes.
-        // One exceptional case. When 3P launcher is used and a user taps a task screenshot in
-        // task switcher (isRecentsInOpening=true), app transition must start even though
-        // recents is running. Otherwise app transition is blocked until timeout (b/232984498).
-        // When 1P launcher is used, this animation is controlled by the launcher outside of
-        // the app transition, so delaying app transition doesn't cause visible delay. After
-        // recents finishes, app transition is handled just to commit visibility on apps.
-        if (!isRecentsInOpening) {
-            final ArraySet<WindowContainer> participants = new ArraySet<>();
-            participants.addAll(mDisplayContent.mOpeningApps);
-            participants.addAll(mDisplayContent.mChangingContainers);
-            boolean deferForRecents = false;
-            for (int i = 0; i < participants.size(); i++) {
-                WindowContainer wc = participants.valueAt(i);
-                final ActivityRecord activity = getAppFromContainer(wc);
-                if (activity == null) {
-                    continue;
-                }
-                // Don't defer recents animation if one of activity isn't running for it, that one
-                // might be started from quickstep.
-                if (!activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
-                    deferForRecents = false;
-                    break;
-                }
-                deferForRecents = true;
-            }
-            if (deferForRecents) {
-                ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
-                        "Delaying app transition for recents animation to finish");
-                return;
-            }
-        }
 
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
 
@@ -289,10 +254,12 @@
                 getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
         final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
 
-        // No AE remote animation with Shell transition.
-        // Unfreeze the windows that were previously frozen for TaskFragment animation.
-        unfreezeEmbeddedChangingWindows();
-        overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
+        // Check if there is any override
+        if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
+            // Unfreeze the windows that were previously frozen for TaskFragment animation.
+            unfreezeEmbeddedChangingWindows();
+            overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
+        }
 
         final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
                 || containsVoiceInteraction(mDisplayContent.mOpeningApps);
@@ -723,6 +690,64 @@
     }
 
     /**
+     * Overrides the pending transition with the remote animation defined by the
+     * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
+     * {@link TaskFragment} that are organized by the same organizer.
+     *
+     * @return {@code true} if the transition is overridden.
+     */
+    private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
+            ArraySet<Integer> activityTypes) {
+        if (transitionMayContainNonAppWindows(transit)) {
+            return false;
+        }
+        if (!transitionContainsTaskFragmentWithBoundsOverride()) {
+            // No need to play TaskFragment remote animation if all embedded TaskFragment in the
+            // transition fill the Task.
+            return false;
+        }
+
+        final Task task = findParentTaskForAllEmbeddedWindows();
+        final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
+        final RemoteAnimationDefinition definition = organizer != null
+                ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
+                    .getRemoteAnimationDefinition(organizer)
+                : null;
+        final RemoteAnimationAdapter adapter = definition != null
+                ? definition.getAdapter(transit, activityTypes)
+                : null;
+        if (adapter == null) {
+            return false;
+        }
+        mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
+                adapter, false /* sync */, true /*isActivityEmbedding*/);
+        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+                "Override with TaskFragment remote animation for transit=%s",
+                AppTransition.appTransitionOldToString(transit));
+
+        final int organizerUid = mDisplayContent.mAtmService.mTaskFragmentOrganizerController
+                .getTaskFragmentOrganizerUid(organizer);
+        final boolean shouldDisableInputForRemoteAnimation = !task.isFullyTrustedEmbedding(
+                organizerUid);
+        final RemoteAnimationController remoteAnimationController =
+                mDisplayContent.mAppTransition.getRemoteAnimationController();
+        if (shouldDisableInputForRemoteAnimation && remoteAnimationController != null) {
+            // We are going to use client-driven animation, Disable all input on activity windows
+            // during the animation (unless it is fully trusted) to ensure it is safe to allow
+            // client to animate the surfaces.
+            // This is needed for all activity windows in the animation Task.
+            remoteAnimationController.setOnRemoteAnimationReady(() -> {
+                final Consumer<ActivityRecord> updateActivities =
+                        activity -> activity.setDropInputForAnimation(true);
+                task.forAllActivities(updateActivities);
+            });
+            ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "Task=%d contains embedded TaskFragment."
+                    + " Disabled all input during TaskFragment remote animation.", task.mTaskId);
+        }
+        return true;
+    }
+
+    /**
      * Overrides the pending transition with the remote animation defined for the transition in the
      * set of defined remote animations in the app window token.
      */
@@ -1032,12 +1057,8 @@
     private void applyAnimations(ArraySet<ActivityRecord> openingApps,
             ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,
             LayoutParams animLp, boolean voiceInteraction) {
-        final RecentsAnimationController rac = mService.getRecentsAnimationController();
         if (transit == WindowManager.TRANSIT_OLD_UNSET
                 || (openingApps.isEmpty() && closingApps.isEmpty())) {
-            if (rac != null) {
-                rac.sendTasksAppeared();
-            }
             return;
         }
 
@@ -1075,9 +1096,6 @@
                 voiceInteraction);
         applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
                 voiceInteraction);
-        if (rac != null) {
-            rac.sendTasksAppeared();
-        }
 
         for (int i = 0; i < openingApps.size(); ++i) {
             openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index eb85c1a..f0a6e9e 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -174,11 +174,6 @@
                 mNavBarToken = w.mToken;
                 // Do not animate movable navigation bar (e.g. 3-buttons mode).
                 if (navigationBarCanMove) return;
-                // Or when the navigation bar is currently controlled by recents animation.
-                final RecentsAnimationController recents = mService.getRecentsAnimationController();
-                if (recents != null && recents.isNavigationBarAttachedToApp()) {
-                    return;
-                }
             } else if (navigationBarCanMove || mTransitionOp == OP_CHANGE_MAY_SEAMLESS
                     || mDisplayContent.mTransitionController.mNavigationBarAttachedToApp) {
                 action = Operation.ACTION_SEAMLESS;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 87867f6..28dbc3a 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();
+            }
         }
     }
 
@@ -877,6 +882,7 @@
         } else {
             if (mAnimationHandler.mPrepareCloseTransition != null) {
                 Slog.e(TAG, "Gesture animation is applied on another transition?");
+                return;
             }
             mAnimationHandler.mPrepareCloseTransition = transition;
             if (!migratePredictToTransition) {
@@ -1203,7 +1209,7 @@
         }
 
         void markWindowHasDrawn(ActivityRecord activity) {
-            if (!mComposed || mWaitTransition || mOpenAnimAdaptor.mPreparedOpenTransition == null
+            if (!mComposed || mWaitTransition
                     || mOpenAnimAdaptor.mRequestedStartingSurfaceId == INVALID_TASK_ID) {
                 return;
             }
@@ -1215,6 +1221,10 @@
                 }
                 allWindowDrawn &= next.mAppWindowDrawn;
             }
+            // Do not remove until transition ready.
+            if (!activity.isVisible()) {
+                return;
+            }
             if (allWindowDrawn) {
                 mOpenAnimAdaptor.cleanUpWindowlessSurface(true);
             }
@@ -1289,6 +1299,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 +1838,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/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 20c5f02..2259b5a 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -21,10 +21,10 @@
 import static android.app.ActivityOptions.BackgroundActivityStartMode;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -39,13 +39,15 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
 import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
 import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
+import static com.android.window.flags.Flags.balAdditionalStartModes;
 import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
-import static com.android.window.flags.Flags.balImprovedMetrics;
 import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
+import static com.android.window.flags.Flags.balImprovedMetrics;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
 import static com.android.window.flags.Flags.balRequireOptInSameUid;
 import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
@@ -84,6 +86,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.UiThread;
 import com.android.server.am.PendingIntentRecord;
+import com.android.server.wm.BackgroundLaunchProcessController.BalCheckConfiguration;
 
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
@@ -107,6 +110,17 @@
     private static final int ASM_GRACEPERIOD_MAX_REPEATS = 5;
     private static final int NO_PROCESS_UID = -1;
 
+    private static final BalCheckConfiguration BAL_CHECK_FOREGROUND = new BalCheckConfiguration(
+            /* isCheckingForFgsStarts */ false,
+            /* checkVisibility */ true,
+            /* checkOtherExemptions */ false,
+            ACTIVITY_BG_START_GRACE_PERIOD_MS);
+    private static final BalCheckConfiguration BAL_CHECK_BACKGROUND = new BalCheckConfiguration(
+            /* isCheckingForFgsStarts */ false,
+            /* checkVisibility */ false,
+            /* checkOtherExemptions */ true,
+            ACTIVITY_BG_START_GRACE_PERIOD_MS);
+
     static final String AUTO_OPT_IN_NOT_PENDING_INTENT = "notPendingIntent";
     static final String AUTO_OPT_IN_CALL_FOR_RESULT = "callForResult";
     static final String AUTO_OPT_IN_SAME_UID = "sameUid";
@@ -412,6 +426,8 @@
                 int callingUid, String callingPackage, ActivityOptions checkedOptions) {
             switch (checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()) {
                 case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+                case MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE:
+                case MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS:
                     return BackgroundStartPrivileges.ALLOW_BAL;
                 case MODE_BACKGROUND_ACTIVITY_START_DENIED:
                     return BackgroundStartPrivileges.NONE;
@@ -752,7 +768,7 @@
         // PendingIntents is null).
         BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
                 ? resultForCaller
-                : checkBackgroundActivityStartAllowedBySender(state)
+                : checkBackgroundActivityStartAllowedByRealCaller(state)
                         .setBasedOnRealCaller();
         state.setResultForRealCaller(resultForRealCaller);
 
@@ -827,6 +843,37 @@
      * or {@link #BAL_BLOCK} if the launch should be blocked
      */
     BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) {
+        if (state.isPendingIntent()) {
+            // PendingIntents should mostly be allowed by the sender (real caller) or a permission
+            // the creator of the PendingIntent has. Visibility should be the exceptional case, so
+            // test it last (this does not change the result, just the bal code).
+            BalVerdict result = BalVerdict.BLOCK;
+            if (!(balAdditionalStartModes()
+                    && state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                    == MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE)) {
+                result = checkBackgroundActivityStartAllowedByCallerInBackground(state);
+            }
+            if (result == BalVerdict.BLOCK) {
+                result = checkBackgroundActivityStartAllowedByCallerInForeground(state);
+
+            }
+            return result;
+        } else {
+            BalVerdict result = checkBackgroundActivityStartAllowedByCallerInForeground(state);
+            if (result == BalVerdict.BLOCK && !(balAdditionalStartModes()
+                    && state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                    == MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE)) {
+                result = checkBackgroundActivityStartAllowedByCallerInBackground(state);
+            }
+            return result;
+        }
+    }
+
+    /**
+     * @return A code denoting which BAL rule allows an activity to be started,
+     * or {@link #BAL_BLOCK} if the launch should be blocked
+     */
+    BalVerdict checkBackgroundActivityStartAllowedByCallerInForeground(BalState state) {
         // This is used to block background activity launch even if the app is still
         // visible to user after user clicking home button.
 
@@ -842,7 +889,16 @@
             return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
                     /*background*/ false, "callingUid has non-app visible window");
         }
+        // Don't abort if the callerApp or other processes of that uid are considered to be in the
+        // foreground.
+        return checkProcessAllowsBal(state.mCallerApp, state, BAL_CHECK_FOREGROUND);
+    }
 
+    /**
+     * @return A code denoting which BAL rule allows an activity to be started,
+     * or {@link #BAL_BLOCK} if the launch should be blocked
+     */
+    BalVerdict checkBackgroundActivityStartAllowedByCallerInBackground(BalState state) {
         // don't abort for the most important UIDs
         final int callingAppId = UserHandle.getAppId(state.mCallingUid);
         if (state.mCallingUid == Process.ROOT_UID
@@ -922,25 +978,29 @@
                     "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted");
         }
 
-        // If we don't have callerApp at this point, no caller was provided to startActivity().
-        // That's the case for PendingIntent-based starts, since the creator's process might not be
-        // up and alive.
         // Don't abort if the callerApp or other processes of that uid are allowed in any way.
-        BalVerdict callerAppAllowsBal = checkProcessAllowsBal(state.mCallerApp, state);
-        if (callerAppAllowsBal.allows()) {
-            return callerAppAllowsBal;
-        }
-
-        // If we are here, it means all exemptions based on the creator failed
-        return BalVerdict.BLOCK;
+        return checkProcessAllowsBal(state.mCallerApp, state, BAL_CHECK_BACKGROUND);
     }
 
     /**
      * @return A code denoting which BAL rule allows an activity to be started,
      * or {@link #BAL_BLOCK} if the launch should be blocked
      */
-    BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
+    BalVerdict checkBackgroundActivityStartAllowedByRealCaller(BalState state) {
+        BalVerdict result = checkBackgroundActivityStartAllowedByRealCallerInForeground(state);
+        if (result == BalVerdict.BLOCK && !(balAdditionalStartModes()
+                && state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                == MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE)) {
+            result = checkBackgroundActivityStartAllowedByRealCallerInBackground(state);
+        }
+        return result;
+    }
 
+    /**
+     * @return A code denoting which BAL rule allows an activity to be started,
+     * or {@link #BAL_BLOCK} if the launch should be blocked
+     */
+    BalVerdict checkBackgroundActivityStartAllowedByRealCallerInForeground(BalState state) {
         // Normal apps with visible app window will be allowed to start activity if app switching
         // is allowed, or apps like live wallpaper with non app visible window will be allowed.
         // The home app can start apps even if app switches are usually disallowed.
@@ -966,6 +1026,16 @@
             }
         }
 
+        // Don't abort if the realCallerApp or other processes of that uid are considered to be in
+        // the foreground.
+        return checkProcessAllowsBal(state.mRealCallerApp, state, BAL_CHECK_FOREGROUND);
+    }
+
+    /**
+     * @return A code denoting which BAL rule allows an activity to be started,
+     * or {@link #BAL_BLOCK} if the launch should be blocked
+     */
+    BalVerdict checkBackgroundActivityStartAllowedByRealCallerInBackground(BalState state) {
         if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
                 == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
                 && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
@@ -992,14 +1062,7 @@
         }
 
         // don't abort if the callerApp or other processes of that uid are allowed in any way
-        BalVerdict realCallerAppAllowsBal =
-                checkProcessAllowsBal(state.mRealCallerApp, state);
-        if (realCallerAppAllowsBal.allows()) {
-            return realCallerAppAllowsBal;
-        }
-
-        // If we are here, it means all exemptions based on PI sender failed
-        return BalVerdict.BLOCK;
+        return checkProcessAllowsBal(state.mRealCallerApp, state, BAL_CHECK_BACKGROUND);
     }
 
     @VisibleForTesting boolean hasBalPermission(int uid, int pid) {
@@ -1015,13 +1078,13 @@
      * exceptions.
      */
     @VisibleForTesting BalVerdict checkProcessAllowsBal(WindowProcessController app,
-            BalState state) {
+            BalState state, BalCheckConfiguration balCheckConfiguration) {
         if (app == null) {
             return BalVerdict.BLOCK;
         }
         // first check the original calling process
         final BalVerdict balAllowedForCaller = app
-                .areBackgroundActivityStartsAllowed(state.mAppSwitchState);
+                .areBackgroundActivityStartsAllowed(state.mAppSwitchState, balCheckConfiguration);
         if (balAllowedForCaller.allows()) {
             return balAllowedForCaller.withProcessInfo("callerApp process", app);
         } else {
@@ -1033,7 +1096,7 @@
                     final WindowProcessController proc = uidProcesses.valueAt(i);
                     if (proc != app) {
                         BalVerdict balAllowedForUid = proc.areBackgroundActivityStartsAllowed(
-                                state.mAppSwitchState);
+                                state.mAppSwitchState, balCheckConfiguration);
                         if (balAllowedForUid.allows()) {
                             return balAllowedForUid.withProcessInfo("process", proc);
                         }
@@ -1685,6 +1748,21 @@
                             (state.mOriginatingPendingIntent != null));
         }
 
+        if (finalVerdict.getRawCode() == BAL_ALLOW_GRACE_PERIOD) {
+            if (state.realCallerExplicitOptInOrAutoOptIn()
+                    && state.mResultForRealCaller.allows()
+                    && state.mResultForRealCaller.getRawCode() != BAL_ALLOW_GRACE_PERIOD) {
+                // real caller could allow with a different exemption
+            } else if (state.callerExplicitOptInOrAutoOptIn() && state.mResultForCaller.allows()
+                    && state.mResultForCaller.getRawCode() != BAL_ALLOW_GRACE_PERIOD) {
+                // caller could allow with a different exemption
+            } else {
+                // log to determine grace period length distribution
+                Slog.wtf(TAG, "Activity start ONLY allowed by BAL_ALLOW_GRACE_PERIOD "
+                        + finalVerdict.mMessage + ": " + state);
+            }
+        }
+
         if (balImprovedMetrics()) {
             if (shouldLogStats(finalVerdict, state)) {
                 String activityName;
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 4a870a3..1073713 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
@@ -48,7 +47,6 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.IntArray;
-import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
@@ -100,60 +98,75 @@
         mBackgroundActivityStartCallback = callback;
     }
 
+    record BalCheckConfiguration(
+            boolean isCheckingForFgsStart,
+            boolean checkVisibility,
+            boolean checkOtherExemptions,
+            long gracePeriod
+    ) {
+    }
+
+    /**
+     * Check configuration for foreground service starts.
+     *
+     * The check executes all parts of the BAL checks and uses the same grace period,
+     * so FGS is allowed whenever BAL is allowed.
+     */
+    static final BalCheckConfiguration CHECK_FOR_FGS_START = new BalCheckConfiguration(
+            /* isCheckingForFgsStarts */ true,
+            /* checkVisibility */ true,
+            /* checkOtherExemptions */ true,
+            ACTIVITY_BG_START_GRACE_PERIOD_MS);
+
     BalVerdict areBackgroundActivityStartsAllowed(
             int pid, int uid, String packageName,
-            int appSwitchState, boolean isCheckingForFgsStart,
+            int appSwitchState, BalCheckConfiguration checkConfiguration,
             boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges,
             long lastStopAppSwitchesTime, long lastActivityLaunchTime,
             long lastActivityFinishTime) {
         // Allow if the proc is instrumenting with background activity starts privs.
-        if (hasBackgroundActivityStartPrivileges) {
+        if (checkConfiguration.checkOtherExemptions && hasBackgroundActivityStartPrivileges) {
             return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true,
                     "process instrumenting with background activity starts privileges");
         }
         // Allow if the flag was explicitly set.
-        if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) {
+        if (checkConfiguration.checkOtherExemptions && isBackgroundStartAllowedByToken(uid,
+                packageName, checkConfiguration.isCheckingForFgsStart)) {
             return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_TOKEN : BAL_ALLOW_PERMISSION,
                     /*background*/ true, "process allowed by token");
         }
         // Allow if the caller is bound by a UID that's currently foreground.
         // But still respect the appSwitchState.
-        boolean allowBoundByForegroundUid =
+        if (checkConfiguration.checkVisibility && (
                 Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid()
-                ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
-                : isBoundByForegroundUid();
-        if (allowBoundByForegroundUid) {
+                        ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
+                        : isBoundByForegroundUid())) {
             return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
                     : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
                     "process bound by foreground uid");
         }
         // Allow if the caller has an activity in any foreground task.
-        if (hasActivityInVisibleTask && appSwitchState != APP_SWITCH_DISALLOW) {
+        if (checkConfiguration.checkVisibility && hasActivityInVisibleTask
+                && appSwitchState != APP_SWITCH_DISALLOW) {
             return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false,
                     "process has activity in foreground task");
         }
 
         // If app switching is not allowed, we ignore all the start activity grace period
         // exception so apps cannot start itself in onPause() after pressing home button.
-        if (appSwitchState == APP_SWITCH_ALLOW) {
+        if (checkConfiguration.checkOtherExemptions && appSwitchState == APP_SWITCH_ALLOW) {
             // Allow if any activity in the caller has either started or finished very recently, and
             // it must be started or finished after last stop app switches time.
-            final long now = SystemClock.uptimeMillis();
-            if (now - lastActivityLaunchTime < ACTIVITY_BG_START_GRACE_PERIOD_MS
-                    || now - lastActivityFinishTime < ACTIVITY_BG_START_GRACE_PERIOD_MS) {
-                // If activity is started and finished before stop app switch time, we should not
-                // let app to be able to start background activity even it's in grace period.
-                if (lastActivityLaunchTime > lastStopAppSwitchesTime
-                        || lastActivityFinishTime > lastStopAppSwitchesTime) {
+            if (lastActivityLaunchTime > lastStopAppSwitchesTime
+                    || lastActivityFinishTime > lastStopAppSwitchesTime) {
+                final long now = SystemClock.uptimeMillis();
+                long timeSinceLastStartOrFinish = now - Math.max(lastActivityLaunchTime,
+                        lastActivityFinishTime);
+                if (timeSinceLastStartOrFinish < checkConfiguration.gracePeriod) {
                     return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true,
-                            "within " + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period");
+                            "within " + checkConfiguration.gracePeriod + "ms grace period ("
+                                    + timeSinceLastStartOrFinish + "ms)");
                 }
-                if (DEBUG_ACTIVITY_STARTS) {
-                    Slog.d(TAG, "[Process(" + pid + ")] Activity start within "
-                            + ACTIVITY_BG_START_GRACE_PERIOD_MS
-                            + "ms grace period but also within stop app switch window");
-                }
-
             }
         }
         return BalVerdict.BLOCK;
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 9be3f43..670a61d 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -195,12 +195,14 @@
      * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
      * All overrides to those fields should be in this method.
      *
+     * Task is only needed for split-screen to apply an offset special handling.
+     *
      * TODO: Consider integrate this with computeConfigByResolveHint()
      */
     static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo,
             Configuration newParentConfiguration, Configuration inOutConfig,
             boolean optsOutEdgeToEdge, boolean hasFixedRotationTransform,
-            boolean hasCompatDisplayInsets) {
+            boolean hasCompatDisplayInsets, Task task) {
         if (displayContent == null) {
             return;
         }
@@ -223,13 +225,16 @@
         }
         if (!optsOutEdgeToEdge && (!useOverrideInsetsForConfig
                 || hasCompatDisplayInsets
-                || isFloating
                 || rotation == ROTATION_UNDEFINED)) {
             // If the insets configuration decoupled logic is not enabled for the app, or the app
             // already has a compat override, or the context doesn't contain enough info to
             // calculate the override, skip the override.
             return;
         }
+        if (isFloating) {
+            // Floating window won't have any insets affect configuration. Skip the override.
+            return;
+        }
         // Make sure the orientation related fields will be updated by the override insets, because
         // fixed rotation has assigned the fields from display's configuration.
         if (hasFixedRotationTransform) {
@@ -255,17 +260,17 @@
             inOutConfig.windowConfiguration.setAppBounds(
                     newParentConfiguration.windowConfiguration.getBounds());
             outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-            if (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-                final DisplayPolicy.DecorInsets.Info decor =
-                        displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
-                if (outAppBounds.contains(decor.mOverrideNonDecorFrame)) {
-                    outAppBounds.intersect(decor.mOverrideNonDecorFrame);
+            if (task != null) {
+                task = task.getCreatedByOrganizerTask();
+                if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
+                    outAppBounds.offset(task.mOffsetXForInsets, task.mOffsetYForInsets);
                 }
-            } else {
-                // TODO(b/358509380): Handle other windowing mode like split screen and freeform
-                //  cases correctly.
-                outAppBounds.inset(displayContent.getDisplayPolicy()
-                        .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+            }
+            final DisplayPolicy.DecorInsets.Info decor =
+                    displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+            outAppBounds.intersectUnchecked(decor.mOverrideNonDecorFrame);
+            if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) {
+                outAppBounds.offset(-task.mOffsetXForInsets, -task.mOffsetYForInsets);
             }
         }
         float density = inOutConfig.densityDpi;
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index b936556..ff1742b 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -104,7 +104,7 @@
      * resizability.
      */
     private float getFixedOrientationLetterboxAspectRatio(@NonNull Task task) {
-        return mActivityRecord.shouldCreateCompatDisplayInsets()
+        return mActivityRecord.shouldCreateAppCompatDisplayInsets()
                 ? getDefaultMinAspectRatioForUnresizableApps(task)
                 : getDefaultMinAspectRatio(task);
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 129931e..97d35b9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -235,7 +235,6 @@
 import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
-import android.view.SurfaceSession;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager;
@@ -572,8 +571,6 @@
 
     boolean mWallpaperMayChange = false;
 
-    private final SurfaceSession mSession = new SurfaceSession();
-
     /**
      * A perf hint session which will boost the refresh rate for the display and change sf duration
      * to handle larger workloads.
@@ -951,6 +948,7 @@
                     w.updateLastFrames();
                     mWmService.mFrameChangingWindows.remove(w);
                 }
+                w.updateSurfacePositionNonOrganized();
                 w.onResizeHandled();
             }
 
@@ -1283,7 +1281,7 @@
      * @param transaction as part of which to perform the configuration
      */
     private void configureSurfaces(Transaction transaction) {
-        final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
+        final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder()
                 .setOpaque(true)
                 .setContainerLayer()
                 .setCallsite("DisplayContent");
@@ -1834,7 +1832,7 @@
         if (mTransitionController.useShellTransitionsRotation()) {
             return ROTATION_UNDEFINED;
         }
-        final int activityOrientation = r.getOverrideOrientation();
+        int activityOrientation = r.getOverrideOrientation();
         if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM
                 || shouldIgnoreOrientationRequest(activityOrientation)) {
             return ROTATION_UNDEFINED;
@@ -1845,14 +1843,15 @@
                     r /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
             if (nextCandidate != null) {
                 r = nextCandidate;
+                activityOrientation = r.getOverrideOrientation();
             }
         }
-        if (r.inMultiWindowMode() || r.getRequestedConfigurationOrientation(true /* forDisplay */)
-                == getConfiguration().orientation) {
+        if (r.inMultiWindowMode() || r.getRequestedConfigurationOrientation(true /* forDisplay */,
+                activityOrientation) == getConfiguration().orientation) {
             return ROTATION_UNDEFINED;
         }
         final int currentRotation = getRotation();
-        final int rotation = mDisplayRotation.rotationForOrientation(r.getRequestedOrientation(),
+        final int rotation = mDisplayRotation.rotationForOrientation(activityOrientation,
                 currentRotation);
         if (rotation == currentRotation) {
             return ROTATION_UNDEFINED;
@@ -1935,7 +1934,6 @@
             return false;
         }
         if (mLastWallpaperVisible && r.windowsCanBeWallpaperTarget()
-                && mFixedRotationTransitionListener.mAnimatingRecents == null
                 && !mTransitionController.isTransientLaunch(r)) {
             // Use normal rotation animation for orientation change of visible wallpaper if recents
             // animation is not running (it may be swiping to home).
@@ -1961,9 +1959,7 @@
 
     /** Returns {@code true} if the top activity is transformed with the new rotation of display. */
     boolean hasTopFixedRotationLaunchingApp() {
-        return mFixedRotationLaunchingApp != null
-                // Ignore animating recents because it hasn't really become the top.
-                && mFixedRotationLaunchingApp != mFixedRotationTransitionListener.mAnimatingRecents;
+        return mFixedRotationLaunchingApp != null;
     }
 
     /** It usually means whether the recents activity is launching with a different rotation. */
@@ -1990,8 +1986,7 @@
             mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
             // Delay the hide animation to avoid blinking by clicking navigation bar that may
             // toggle fixed rotation in a short time.
-            final boolean shouldDebounce = r == mFixedRotationTransitionListener.mAnimatingRecents
-                    || mTransitionController.isTransientLaunch(r);
+            final boolean shouldDebounce = mTransitionController.isTransientLaunch(r);
             startAsyncRotation(shouldDebounce);
         } else if (mFixedRotationLaunchingApp != null && r == null) {
             mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
@@ -2023,12 +2018,9 @@
             // the heavy operations. This also benefits that the states of multiple activities
             // are handled together.
             r.linkFixedRotationTransform(prevRotatedLaunchingApp);
-            if (r != mFixedRotationTransitionListener.mAnimatingRecents) {
-                // Only update the record for normal activity so the display orientation can be
-                // updated when the transition is done if it becomes the top. And the case of
-                // recents can be handled when the recents animation is finished.
-                setFixedRotationLaunchingAppUnchecked(r, rotation);
-            }
+            // Only update the record for normal activity so the display orientation can be
+            // updated when the transition is done if it becomes the top.
+            setFixedRotationLaunchingAppUnchecked(r, rotation);
             return;
         }
 
@@ -3220,15 +3212,13 @@
      * If xPpi or yDpi is equal to {@link #INVALID_DPI}, the values are ignored.
      */
     void setForcedSize(int width, int height, float xDPI, float yDPI) {
-  	// Can't force size higher than the maximal allowed
-        if (mMaxUiWidth > 0 && width > mMaxUiWidth) {
-            final float ratio = mMaxUiWidth / (float) width;
-            height = (int) (height * ratio);
-            width = mMaxUiWidth;
-        }
-
         mIsSizeForced = mInitialDisplayWidth != width || mInitialDisplayHeight != height;
         if (mIsSizeForced) {
+            if (mMaxUiWidth > 0 && width > mMaxUiWidth) {
+                final float ratio = mMaxUiWidth / (float) width;
+                height = (int) (height * ratio);
+                width = mMaxUiWidth;
+            }
             final Point size = getValidForcedSize(width, height);
             width = size.x;
             height = size.y;
@@ -4602,7 +4592,7 @@
         // removed on the task.
         removeImeSurfaceImmediately();
         mImeScreenshot = new ImeScreenshot(
-                mWmService.mSurfaceControlFactory.apply(null), imeTarget);
+                mWmService.mSurfaceControlFactory.get(), imeTarget);
         // If the caller requests to hide IME, then allow to show IME snapshot for any target task.
         // So IME won't look like suddenly disappeared. It usually happens when turning off screen.
         mImeScreenshot.attachAndShow(t, hideImeWindow /* anyTargetTask */);
@@ -5435,14 +5425,8 @@
     }
 
     @Override
-    SurfaceSession getSession() {
-        return mSession;
-    }
-
-    @Override
     SurfaceControl.Builder makeChildSurface(WindowContainer child) {
-        SurfaceSession s = child != null ? child.getSession() : getSession();
-        final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(s).setContainerLayer();
+        final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder().setContainerLayer();
         if (child == null) {
             return b;
         }
@@ -5458,12 +5442,12 @@
      * and other potpourii.
      */
     SurfaceControl.Builder makeOverlay() {
-        return mWmService.makeSurfaceBuilder(mSession).setParent(getOverlayLayer());
+        return mWmService.makeSurfaceBuilder().setParent(getOverlayLayer());
     }
 
     @Override
     public SurfaceControl.Builder makeAnimationLeash() {
-        return mWmService.makeSurfaceBuilder(mSession).setParent(mSurfaceControl)
+        return mWmService.makeSurfaceBuilder().setParent(mSurfaceControl)
                 .setContainerLayer();
     }
 
@@ -5898,18 +5882,13 @@
         final Region local = Region.obtain();
         final int[] remainingLeftRight =
                 {mSystemGestureExclusionLimit, mSystemGestureExclusionLimit};
-        final RecentsAnimationController recentsAnimationController =
-                mWmService.getRecentsAnimationController();
 
         // Traverse all windows top down to assemble the gesture exclusion rects.
         // For each window, we only take the rects that fall within its touchable region.
         forAllWindows(w -> {
-            final boolean ignoreRecentsAnimationTarget = recentsAnimationController != null
-                    && recentsAnimationController.shouldApplyInputConsumer(w.getActivityRecord());
             if (!w.canReceiveTouchInput() || !w.isVisible()
                     || (w.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0
-                    || unhandled.isEmpty()
-                    || ignoreRecentsAnimationTarget) {
+                    || unhandled.isEmpty()) {
                 return;
             }
 
@@ -6121,16 +6100,7 @@
     void getKeepClearAreas(Set<Rect> outRestricted, Set<Rect> outUnrestricted) {
         final Matrix tmpMatrix = new Matrix();
         final float[] tmpFloat9 = new float[9];
-        final RecentsAnimationController recentsAnimationController =
-                mWmService.getRecentsAnimationController();
         forAllWindows(w -> {
-            // Skip the window if it is part of Recents animation
-            final boolean ignoreRecentsAnimationTarget = recentsAnimationController != null
-                    && recentsAnimationController.shouldApplyInputConsumer(w.getActivityRecord());
-            if (ignoreRecentsAnimationTarget) {
-                return false;  // continue traversal
-            }
-
             if (w.isVisible() && !w.inPinnedWindowingMode()) {
                 w.getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9);
 
@@ -6305,14 +6275,6 @@
     }
 
     boolean updateDisplayOverrideConfigurationLocked() {
-        // Preemptively cancel the running recents animation -- SysUI can't currently handle this
-        // case properly since the signals it receives all happen post-change
-        final RecentsAnimationController recentsAnimationController =
-                mWmService.getRecentsAnimationController();
-        if (recentsAnimationController != null) {
-            recentsAnimationController.cancelAnimationForDisplayChange();
-        }
-
         Configuration values = new Configuration();
         computeScreenConfiguration(values);
 
@@ -6913,79 +6875,11 @@
     /** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */
     class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener {
 
-        /**
-         * The animating activity which shows the recents task list. It is set between
-         * {@link RecentsAnimationController#initialize} and
-         * {@link RecentsAnimationController#cleanupAnimation}.
-         */
-        private ActivityRecord mAnimatingRecents;
-
-        /** Whether {@link #mAnimatingRecents} is going to be the top activity. */
-        private boolean mRecentsWillBeTop;
-
         FixedRotationTransitionListener(int displayId) {
             super(displayId);
         }
 
         /**
-         * If the recents activity has a fixed orientation which is different from the current top
-         * activity, it will be rotated before being shown so we avoid a screen rotation animation
-         * when showing the Recents view.
-         */
-        void onStartRecentsAnimation(@NonNull ActivityRecord r) {
-            mAnimatingRecents = r;
-            if (r.isVisible() && mFocusedApp != null && !mFocusedApp.occludesParent()) {
-                // The recents activity has shown with the orientation determined by the top
-                // activity, keep its current orientation to avoid flicking by the configuration
-                // change of visible activity.
-                return;
-            }
-            rotateInDifferentOrientationIfNeeded(r);
-            if (r.hasFixedRotationTransform()) {
-                // Set the record so we can recognize it to continue to update display orientation
-                // if the recents activity becomes the top later.
-                setFixedRotationLaunchingApp(r, r.getWindowConfiguration().getRotation());
-            }
-        }
-
-        /**
-         * If {@link #mAnimatingRecents} still has fixed rotation, it should be moved to top so we
-         * don't clear {@link #mFixedRotationLaunchingApp} that will be handled by transition.
-         */
-        void onFinishRecentsAnimation() {
-            final ActivityRecord animatingRecents = mAnimatingRecents;
-            final boolean recentsWillBeTop = mRecentsWillBeTop;
-            mAnimatingRecents = null;
-            mRecentsWillBeTop = false;
-            if (recentsWillBeTop) {
-                // The recents activity will be the top, such as staying at recents list or
-                // returning to home (if home and recents are the same activity).
-                return;
-            }
-
-            if (animatingRecents != null && animatingRecents == mFixedRotationLaunchingApp
-                    && animatingRecents.isVisible() && animatingRecents != topRunningActivity()) {
-                // The recents activity should be going to be invisible (switch to another app or
-                // return to original top). Only clear the top launching record without finishing
-                // the transform immediately because it won't affect display orientation. And before
-                // the visibility is committed, the recents activity may perform relayout which may
-                // cause unexpected configuration change if the rotated configuration is restored.
-                // The transform will be finished when the transition is done.
-                setFixedRotationLaunchingAppUnchecked(null);
-            } else {
-                // If there is already a launching activity that is not the recents, before its
-                // transition is completed, the recents animation may be started. So if the recents
-                // activity won't be the top, the display orientation should be updated according
-                // to the current top activity.
-                continueUpdateOrientationForDiffOrienLaunchingApp();
-            }
-        }
-
-        void notifyRecentsWillBeTop() {
-            mRecentsWillBeTop = true;
-        }
-
-        /**
          * Returns {@code true} if the transient launch (e.g. recents animation) requested a fixed
          * orientation, then the rotation change should be deferred.
          */
@@ -6995,8 +6889,6 @@
                 if (hasFixedRotationTransientLaunch()) {
                     source = mFixedRotationLaunchingApp;
                 }
-            } else if (mAnimatingRecents != null && !hasTopFixedRotationLaunchingApp()) {
-                source = mAnimatingRecents;
             }
             if (source == null || source.getRequestedConfigurationOrientation(
                     true /* forDisplay */) == ORIENTATION_UNDEFINED) {
@@ -7009,19 +6901,7 @@
         @Override
         public void onAppTransitionFinishedLocked(IBinder token) {
             final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-            // Ignore the animating recents so the fixed rotation transform won't be switched twice
-            // by finishing the recents animation and moving it to top. That also avoids flickering
-            // due to wait for previous activity to be paused if it supports PiP that ignores the
-            // effect of resume-while-pausing.
-            if (r == null || r == mAnimatingRecents) {
-                return;
-            }
-            if (mAnimatingRecents != null && mRecentsWillBeTop) {
-                // The activity is not the recents and it should be moved to back later, so it is
-                // better to keep its current appearance for the next transition. Otherwise the
-                // display orientation may be updated too early and the layout procedures at the
-                // end of finishing recents animation is skipped. That causes flickering because
-                // the surface of closing app hasn't updated to invisible.
+            if (r == null) {
                 return;
             }
             if (mFixedRotationLaunchingApp == null) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 745b792..5c62120 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1036,7 +1036,7 @@
     /**
      * Check if a window can be added to the system.
      *
-     * Currently enforces that two window types are singletons per display:
+     * Currently enforces that these window types are singletons per display:
      * <ul>
      * <li>{@link WindowManager.LayoutParams#TYPE_STATUS_BAR}</li>
      * <li>{@link WindowManager.LayoutParams#TYPE_NOTIFICATION_SHADE}</li>
@@ -1058,41 +1058,39 @@
             ActivityTaskManagerService.enforceTaskPermission("DisplayPolicy");
         }
 
+        final String systemUiPermission =
+                mService.isCallerVirtualDeviceOwner(mDisplayContent.getDisplayId(), callingUid)
+                        // Allow virtual device owners to add system windows on their displays.
+                        ? android.Manifest.permission.CREATE_VIRTUAL_DEVICE
+                        : android.Manifest.permission.STATUS_BAR_SERVICE;
+
         switch (attrs.type) {
             case TYPE_STATUS_BAR:
-                mContext.enforcePermission(
-                        android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+                mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
                         "DisplayPolicy");
                 if (mStatusBar != null && mStatusBar.isAlive()) {
                     return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
             case TYPE_NOTIFICATION_SHADE:
-                mContext.enforcePermission(
-                        android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+                mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
                         "DisplayPolicy");
-                if (mNotificationShade != null) {
-                    if (mNotificationShade.isAlive()) {
-                        return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                    }
+                if (mNotificationShade != null && mNotificationShade.isAlive()) {
+                    return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
             case TYPE_NAVIGATION_BAR:
-                mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE,
-                        callingPid, callingUid, "DisplayPolicy");
+                mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
+                        "DisplayPolicy");
                 if (mNavigationBar != null && mNavigationBar.isAlive()) {
                     return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
             case TYPE_NAVIGATION_BAR_PANEL:
-                mContext.enforcePermission(android.Manifest.permission.STATUS_BAR_SERVICE,
-                        callingPid, callingUid, "DisplayPolicy");
-                break;
             case TYPE_STATUS_BAR_ADDITIONAL:
             case TYPE_STATUS_BAR_SUB_PANEL:
             case TYPE_VOICE_INTERACTION_STARTING:
-                mContext.enforcePermission(
-                        android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+                mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
                         "DisplayPolicy");
                 break;
             case TYPE_STATUS_BAR_PANEL:
@@ -1102,8 +1100,7 @@
         if (attrs.providedInsets != null) {
             // Recents component is allowed to add inset types.
             if (!mService.mAtmService.isCallerRecents(callingUid)) {
-                mContext.enforcePermission(
-                        android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+                mContext.enforcePermission(systemUiPermission, callingPid, callingUid,
                         "DisplayPolicy");
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a5da5e7..5200e82 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -611,16 +611,6 @@
             mDisplayRotationCoordinator.onDefaultDisplayRotationChanged(rotation);
         }
 
-        // Preemptively cancel the running recents animation -- SysUI can't currently handle this
-        // case properly since the signals it receives all happen post-change. We do this earlier
-        // in the rotation flow, since DisplayContent.updateDisplayOverrideConfigurationLocked seems
-        // to happen too late.
-        final RecentsAnimationController recentsAnimationController =
-                mService.getRecentsAnimationController();
-        if (recentsAnimationController != null) {
-            recentsAnimationController.cancelAnimationForDisplayChange();
-        }
-
         ProtoLog.v(WM_DEBUG_ORIENTATION,
                 "Display id=%d rotation changed to %d from %d, lastOrientation=%d",
                         displayId, rotation, oldRotation, lastOrientation);
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index f40f2617..e585efa 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -69,6 +69,7 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
+    /** Stores the size override settings. If the width or height is zero, it means to clear. */
     void setForcedSize(@NonNull DisplayContent displayContent, int width, int height) {
         if (displayContent.isDefaultDisplay) {
             final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 59435b8..b09d63f 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -175,7 +175,7 @@
      */
     private CompletableFuture<Void> showInputSurface() {
         if (mInputSurface == null) {
-            mInputSurface = mService.makeSurfaceBuilder(mDisplayContent.getSession())
+            mInputSurface = mService.makeSurfaceBuilder()
                     .setContainerLayer()
                     .setName("Drag and Drop Input Consumer")
                     .setCallsite("DragState.showInputSurface")
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 4204670..a288cc7 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -77,8 +77,7 @@
         mWindowHandle.scaleFactor = 1.0f;
         mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE;
 
-        mInputSurface = mService.makeSurfaceBuilder(
-                        mService.mRoot.getDisplayContent(displayId).getSession())
+        mInputSurface = mService.makeSurfaceBuilder()
                 .setContainerLayer()
                 .setName("Input Consumer " + name)
                 .setCallsite("InputConsumerImpl")
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1e7de2b..232c3b6 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -281,7 +281,7 @@
                         + " - Input overlay layer is not initialized.");
                 return null;
             }
-            return mService.makeSurfaceBuilder(dc.getSession())
+            return mService.makeSurfaceBuilder()
                     .setContainerLayer()
                     .setName(name)
                     .setCallsite("createSurfaceForGestureMonitor")
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index b8869f1..ddbfd70 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -49,7 +49,6 @@
 import static java.lang.Integer.MAX_VALUE;
 
 import android.annotation.Nullable;
-import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Handler;
 import android.os.IBinder;
@@ -111,7 +110,7 @@
      * draw the live-tile above the recents activity, we also need to provide that activity as a
      * z-layering reference so that we can place the recents input consumer above it.
      */
-    private WeakReference<ActivityRecord> mActiveRecentsActivity = null;
+    private WeakReference<Task> mActiveRecentsTask = null;
     private WeakReference<Task> mActiveRecentsLayerRef = null;
 
     private class UpdateInputWindows implements Runnable {
@@ -388,13 +387,13 @@
 
     /**
      * Inform InputMonitor when recents is active so it can enable the recents input consumer.
-     * @param activity The active recents activity. {@code null} means recents is not active.
+     * @param task The active recents task. {@code null} means recents is not active.
      * @param layer A task whose Z-layer is used as a reference for how to sort the consumer.
      */
-    void setActiveRecents(@Nullable ActivityRecord activity, @Nullable Task layer) {
-        final boolean clear = activity == null;
-        final boolean wasActive = mActiveRecentsActivity != null && mActiveRecentsLayerRef != null;
-        mActiveRecentsActivity = clear ? null : new WeakReference<>(activity);
+    void setActiveRecents(@Nullable Task task, @Nullable Task layer) {
+        final boolean clear = task == null;
+        final boolean wasActive = mActiveRecentsTask != null && mActiveRecentsLayerRef != null;
+        mActiveRecentsTask = clear ? null : new WeakReference<>(task);
         mActiveRecentsLayerRef = clear ? null : new WeakReference<>(layer);
         if (clear && wasActive) {
             setUpdateInputWindowsNeededLw();
@@ -413,17 +412,12 @@
         // Request focus for the recents animation input consumer if an input consumer should
         // be applied for the window.
         if (recentsAnimationInputConsumer != null && focus != null) {
-            final RecentsAnimationController recentsAnimationController =
-                    mService.getRecentsAnimationController();
             // Apply recents input consumer when the focusing window is in recents animation.
-            final boolean shouldApplyRecentsInputConsumer = (recentsAnimationController != null
-                    && recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord))
-                    // Shell transitions doesn't use RecentsAnimationController but we still
-                    // have carryover legacy logic that relies on the consumer.
-                    || (getWeak(mActiveRecentsActivity) != null && focus.inTransition()
+            final boolean shouldApplyRecentsInputConsumer =
+                    getWeak(mActiveRecentsTask) != null && focus.inTransition()
                             // only take focus from the recents activity to avoid intercepting
                             // events before the gesture officially starts.
-                            && focus.isActivityTypeHomeOrRecents());
+                            && focus.isActivityTypeHomeOrRecents();
             if (shouldApplyRecentsInputConsumer) {
                 if (mInputFocus != recentsAnimationInputConsumer.mWindowHandle.token) {
                     requestFocus(recentsAnimationInputConsumer.mWindowHandle.token,
@@ -569,7 +563,6 @@
         private boolean mAddRecentsAnimationInputConsumerHandle;
 
         private boolean mInDrag;
-        private final Rect mTmpRect = new Rect();
 
         private void updateInputWindows(boolean inDrag) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
@@ -586,18 +579,15 @@
 
             resetInputConsumers(mInputTransaction);
             // Update recents input consumer layer if active
-            final ActivityRecord activeRecents = getWeak(mActiveRecentsActivity);
+            final Task activeRecents = getWeak(mActiveRecentsTask);
             if (mAddRecentsAnimationInputConsumerHandle && activeRecents != null
                     && activeRecents.getSurfaceControl() != null) {
                 WindowContainer layer = getWeak(mActiveRecentsLayerRef);
                 layer = layer != null ? layer : activeRecents;
                 // Handle edge-case for SUW where windows don't exist yet
                 if (layer.getSurfaceControl() != null) {
-                    final WindowState targetAppMainWindow = activeRecents.findMainWindow();
-                    if (targetAppMainWindow != null) {
-                        targetAppMainWindow.getBounds(mTmpRect);
-                        mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect);
-                    }
+                    mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
+                            activeRecents.getBounds());
                     mRecentsAnimationInputConsumer.show(mInputTransaction, layer);
                     mAddRecentsAnimationInputConsumerHandle = false;
                 }
@@ -629,24 +619,6 @@
                 return;
             }
 
-            // This only works for legacy transitions.
-            final RecentsAnimationController recentsAnimationController =
-                    mService.getRecentsAnimationController();
-            final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
-                    && recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord);
-            if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
-                if (recentsAnimationController.updateInputConsumerForApp(
-                        mRecentsAnimationInputConsumer.mWindowHandle)) {
-                    final DisplayArea targetDA =
-                            recentsAnimationController.getTargetAppDisplayArea();
-                    if (targetDA != null) {
-                        mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA);
-                        mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 2);
-                        mAddRecentsAnimationInputConsumerHandle = false;
-                    }
-                }
-            }
-
             if (w.inPinnedWindowingMode()) {
                 if (mAddPipInputConsumerHandle) {
                     final Task rootTask = w.getTask().getRootTask();
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index 1a895ea..403d3bd 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -91,7 +91,6 @@
         return (transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
                 || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
                 && displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
-                && service.getRecentsAnimationController() == null
                 && displayContent.getAsyncRotationController() == null;
     }
 
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 781023c..5d6d8bc 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -24,6 +24,7 @@
 per-file Background*Start* = set noparent
 per-file Background*Start* = file:/BAL_OWNERS
 per-file Background*Start* = ogunwale@google.com, louischang@google.com
+per-file BackgroundLaunchProcessController.java = file:/BAL_OWNERS
 
 # File related to activity callers
 per-file ActivityCallerState.java = file:/core/java/android/app/COMPONENT_CALLER_OWNERS
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index c592caf..c06efc7 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -17,82 +17,54 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
-import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.ActivityRecord.State.STOPPING;
-import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
-import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
-import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
-import static com.android.server.wm.TaskDisplayArea.getRootTaskAbove;
 
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.os.RemoteException;
-import android.os.Trace;
 import android.util.Slog;
-import android.view.IRecentsAnimationRunner;
 
 import com.android.internal.protolog.ProtoLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
-import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
-import com.android.server.wm.TaskDisplayArea.OnRootTaskOrderChangedListener;
 
 /**
  * Manages the recents animation, including the reordering of the root tasks for the transition and
  * cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
  */
-class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChangedListener {
+class RecentsAnimation {
     private static final String TAG = RecentsAnimation.class.getSimpleName();
 
-    private final ActivityTaskManagerService mService;
     private final ActivityTaskSupervisor mTaskSupervisor;
     private final ActivityStartController mActivityStartController;
-    private final WindowManagerService mWindowManager;
     private final TaskDisplayArea mDefaultTaskDisplayArea;
     private final Intent mTargetIntent;
     private final ComponentName mRecentsComponent;
     private final @Nullable String mRecentsFeatureId;
     private final int mRecentsUid;
-    private final @Nullable WindowProcessController mCaller;
     private final int mUserId;
     private final int mTargetActivityType;
 
-    /**
-     * The activity which has been launched behind. We need to remember the activity because the
-     * target root task may have other activities, then we are able to restore the launch-behind
-     * state for the exact activity.
-     */
-    private ActivityRecord mLaunchedTargetActivity;
-
-    // The root task to restore the target root task behind when the animation is finished
-    private Task mRestoreTargetBehindRootTask;
-
     RecentsAnimation(ActivityTaskManagerService atm, ActivityTaskSupervisor taskSupervisor,
-            ActivityStartController activityStartController, WindowManagerService wm,
+            ActivityStartController activityStartController,
             Intent targetIntent, ComponentName recentsComponent, @Nullable String recentsFeatureId,
-            int recentsUid, @Nullable WindowProcessController caller) {
-        mService = atm;
+            int recentsUid) {
         mTaskSupervisor = taskSupervisor;
-        mDefaultTaskDisplayArea = mService.mRootWindowContainer.getDefaultTaskDisplayArea();
+        mDefaultTaskDisplayArea = atm.mRootWindowContainer.getDefaultTaskDisplayArea();
         mActivityStartController = activityStartController;
-        mWindowManager = wm;
         mTargetIntent = targetIntent;
         mRecentsComponent = recentsComponent;
         mRecentsFeatureId = recentsFeatureId;
         mRecentsUid = recentsUid;
-        mCaller = caller;
         mUserId = atm.getCurrentUserId();
         mTargetActivityType = targetIntent.getComponent() != null
                 && recentsComponent.equals(targetIntent.getComponent())
@@ -171,310 +143,6 @@
         }
     }
 
-    void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner, long eventTime) {
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");
-
-        // Cancel any existing recents animation running synchronously (do not hold the
-        // WM lock) before starting the newly requested recents animation as they can not coexist
-        if (mWindowManager.getRecentsAnimationController() != null) {
-            mWindowManager.getRecentsAnimationController().forceCancelAnimation(
-                    REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
-        }
-
-        // If the activity is associated with the root recents task, then try and get that first
-        Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
-                mTargetActivityType);
-        ActivityRecord targetActivity = getTargetActivity(targetRootTask);
-        final boolean hasExistingActivity = targetActivity != null;
-        if (hasExistingActivity) {
-            mRestoreTargetBehindRootTask = getRootTaskAbove(targetRootTask);
-            if (mRestoreTargetBehindRootTask == null
-                    && targetRootTask.getTopMostTask() == targetActivity.getTask()) {
-                notifyAnimationCancelBeforeStart(recentsAnimationRunner);
-                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                        "No root task above target root task=%s", targetRootTask);
-                return;
-            }
-        }
-
-        // Send launch hint if we are actually launching the target. If it's already visible
-        // (shouldn't happen in general) we don't need to send it.
-        if (targetActivity == null || !targetActivity.isVisibleRequested()) {
-            mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(
-                    true /* forceSend */, targetActivity);
-        }
-
-        final LaunchingState launchingState =
-                mTaskSupervisor.getActivityMetricsLogger().notifyActivityLaunching(mTargetIntent);
-
-        setProcessAnimating(true);
-
-        mService.deferWindowLayout();
-        try {
-            if (hasExistingActivity) {
-                // Move the recents activity into place for the animation if it is not top most
-                mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(targetRootTask);
-                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved rootTask=%s behind rootTask=%s",
-                        targetRootTask, getRootTaskAbove(targetRootTask));
-
-                // If there are multiple tasks in the target root task (ie. the root home task,
-                // with 3p and default launchers coexisting), then move the task to the top as a
-                // part of moving the root task to the front
-                final Task task = targetActivity.getTask();
-                if (targetRootTask.getTopMostTask() != task) {
-                    targetRootTask.positionChildAtTop(task);
-                }
-            } else {
-                // No recents activity, create the new recents activity bottom most
-                startRecentsActivityInBackground("startRecentsActivity_noTargetActivity");
-
-                // Move the recents activity into place for the animation
-                targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
-                        mTargetActivityType);
-                targetActivity = getTargetActivity(targetRootTask);
-                mDefaultTaskDisplayArea.moveRootTaskBehindBottomMostVisibleRootTask(targetRootTask);
-                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Moved rootTask=%s behind rootTask=%s",
-                        targetRootTask, getRootTaskAbove(targetRootTask));
-
-                mWindowManager.prepareAppTransitionNone();
-                mWindowManager.executeAppTransition();
-
-                // TODO: Maybe wait for app to draw in this particular case?
-
-                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Started intent=%s", mTargetIntent);
-            }
-
-            // Mark the target activity as launch-behind to bump its visibility for the
-            // duration of the gesture that is driven by the recents component
-            targetActivity.mLaunchTaskBehind = true;
-            mLaunchedTargetActivity = targetActivity;
-            // TODO(b/156772625): Evaluate to send new intents vs. replacing the intent extras.
-            targetActivity.intent.replaceExtras(mTargetIntent);
-
-            // Fetch all the surface controls and pass them to the client to get the animation
-            // started
-            mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
-                    this, mDefaultTaskDisplayArea.getDisplayId(),
-                    mTaskSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
-
-            // If we updated the launch-behind state, update the visibility of the activities after
-            // we fetch the visible tasks to be controlled by the animation
-            mService.mRootWindowContainer.ensureActivitiesVisible();
-
-            ActivityOptions options = null;
-            if (eventTime > 0) {
-                options = ActivityOptions.makeBasic();
-                options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
-            }
-            mTaskSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState,
-                    START_TASK_TO_FRONT, !hasExistingActivity, targetActivity, options);
-
-            // Register for root task order changes
-            mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(this);
-        } catch (Exception e) {
-            Slog.e(TAG, "Failed to start recents activity", e);
-            throw e;
-        } finally {
-            mService.continueWindowLayout();
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        }
-    }
-
-    private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode,
-            boolean sendUserLeaveHint) {
-        synchronized (mService.mGlobalLock) {
-            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                    "onAnimationFinished(): controller=%s reorderMode=%d",
-                            mWindowManager.getRecentsAnimationController(), reorderMode);
-
-            // Unregister for root task order changes
-            mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(this);
-
-            final RecentsAnimationController controller =
-                    mWindowManager.getRecentsAnimationController();
-            if (controller == null) return;
-
-            // Just to be sure end the launch hint in case the target activity was never launched.
-            // However, if we're keeping the activity and making it visible, we can leave it on.
-            if (reorderMode != REORDER_KEEP_IN_PLACE) {
-                mService.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
-            }
-
-            // Once the target is shown, prevent spurious background app switches
-            if (reorderMode == REORDER_MOVE_TO_TOP) {
-                mService.stopAppSwitches();
-            }
-
-            inSurfaceTransaction(() -> {
-                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
-                        "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
-                mService.deferWindowLayout();
-                try {
-                    mWindowManager.cleanupRecentsAnimation(reorderMode);
-
-                    final Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(
-                            WINDOWING_MODE_UNDEFINED, mTargetActivityType);
-                    // Prefer to use the original target activity instead of top activity because
-                    // we may have moved another task to top (starting 3p launcher).
-                    final ActivityRecord targetActivity = targetRootTask != null
-                            ? targetRootTask.isInTask(mLaunchedTargetActivity)
-                            : null;
-                    ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                            "onAnimationFinished(): targetRootTask=%s targetActivity=%s "
-                                    + "mRestoreTargetBehindRootTask=%s",
-                            targetRootTask, targetActivity, mRestoreTargetBehindRootTask);
-                    if (targetActivity == null) {
-                        return;
-                    }
-
-                    // Restore the launched-behind state
-                    targetActivity.mLaunchTaskBehind = false;
-
-                    if (reorderMode == REORDER_MOVE_TO_TOP) {
-                        // Bring the target root task to the front
-                        mTaskSupervisor.mNoAnimActivities.add(targetActivity);
-
-                        if (sendUserLeaveHint) {
-                            // Setting this allows the previous app to PiP.
-                            mTaskSupervisor.mUserLeaving = true;
-                            targetRootTask.moveTaskToFront(targetActivity.getTask(),
-                                    true /* noAnimation */, null /* activityOptions */,
-                                    targetActivity.appTimeTracker,
-                                    "RecentsAnimation.onAnimationFinished()");
-                        } else {
-                            targetRootTask.moveToFront("RecentsAnimation.onAnimationFinished()");
-                        }
-
-                        if (WM_DEBUG_RECENTS_ANIMATIONS.isLogToAny()) {
-                            final Task topRootTask = getTopNonAlwaysOnTopRootTask();
-                            if (topRootTask != targetRootTask) {
-                                ProtoLog.w(WM_DEBUG_RECENTS_ANIMATIONS,
-                                        "Expected target rootTask=%s"
-                                        + " to be top most but found rootTask=%s",
-                                        targetRootTask, topRootTask);
-                            }
-                        }
-                    } else if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION){
-                        // Restore the target root task to its previous position
-                        final TaskDisplayArea taskDisplayArea = targetActivity.getDisplayArea();
-                        taskDisplayArea.moveRootTaskBehindRootTask(targetRootTask,
-                                mRestoreTargetBehindRootTask);
-                        if (WM_DEBUG_RECENTS_ANIMATIONS.isLogToAny()) {
-                            final Task aboveTargetRootTask = getRootTaskAbove(targetRootTask);
-                            if (mRestoreTargetBehindRootTask != null
-                                    && aboveTargetRootTask != mRestoreTargetBehindRootTask) {
-                                ProtoLog.w(WM_DEBUG_RECENTS_ANIMATIONS,
-                                        "Expected target rootTask=%s to restored behind "
-                                                + "rootTask=%s but it is behind rootTask=%s",
-                                        targetRootTask, mRestoreTargetBehindRootTask,
-                                        aboveTargetRootTask);
-                            }
-                        }
-                    } else {
-                        // If there is no recents screenshot animation, we can update the visibility
-                        // of target root task immediately because it is visually invisible and the
-                        // launch-behind state is restored. That also prevents the next transition
-                        // type being disturbed if the visibility is updated after setting the next
-                        // transition (the target activity will be one of closing apps).
-                        if (!controller.shouldDeferCancelWithScreenshot()
-                                && !targetRootTask.isFocusedRootTaskOnDisplay()) {
-                            targetRootTask.ensureActivitiesVisible(null /* starting */);
-                        }
-                        // Keep target root task in place, nothing changes, so ignore the transition
-                        // logic below
-                        return;
-                    }
-
-                    mWindowManager.prepareAppTransitionNone();
-                    mService.mRootWindowContainer.ensureActivitiesVisible();
-                    mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
-
-                    // No reason to wait for the pausing activity in this case, as the hiding of
-                    // surfaces needs to be done immediately.
-                    mWindowManager.executeAppTransition();
-
-                    final Task rootTask = targetRootTask.getRootTask();
-                    // Client state may have changed during the recents animation, so force
-                    // send task info so the client can synchronize its state.
-                    rootTask.dispatchTaskInfoChangedIfNeeded(true /* force */);
-                } catch (Exception e) {
-                    Slog.e(TAG, "Failed to clean up recents activity", e);
-                    throw e;
-                } finally {
-                    mTaskSupervisor.mUserLeaving = false;
-                    mService.continueWindowLayout();
-                    // Make sure the surfaces are updated with the latest state. Sometimes the
-                    // surface placement may be skipped if display configuration is changed (i.e.
-                    // {@link DisplayContent#mWaitingForConfig} is true).
-                    if (mWindowManager.mRoot.isLayoutNeeded()) {
-                        mWindowManager.mRoot.performSurfacePlacement();
-                    }
-                    setProcessAnimating(false);
-                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                }
-            });
-        }
-    }
-
-    // No-op wrapper to keep legacy code.
-    private static void inSurfaceTransaction(Runnable exec) {
-        exec.run();
-    }
-
-    /** Gives the owner of recents animation higher priority. */
-    private void setProcessAnimating(boolean animating) {
-        if (mCaller == null) return;
-        // Apply the top-app scheduling group to who runs the animation.
-        mCaller.setRunningRecentsAnimation(animating);
-        int demoteReasons = mService.mDemoteTopAppReasons;
-        if (animating) {
-            demoteReasons |= ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS;
-        } else {
-            demoteReasons &= ~ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS;
-        }
-        mService.mDemoteTopAppReasons = demoteReasons;
-        // Make the demotion of the real top app take effect. No need to restore top app state for
-        // finishing recents because addToStopping -> scheduleIdle -> activityIdleInternal ->
-        // trimApplications will have a full update.
-        if (animating && mService.mTopApp != null) {
-            mService.mTopApp.scheduleUpdateOomAdj();
-        }
-    }
-
-    @Override
-    public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
-            boolean sendUserLeaveHint) {
-        finishAnimation(reorderMode, sendUserLeaveHint);
-    }
-
-    @Override
-    public void onRootTaskOrderChanged(Task rootTask) {
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "onRootTaskOrderChanged(): rootTask=%s", rootTask);
-        if (mDefaultTaskDisplayArea.getRootTask(t -> t == rootTask) == null
-                || !rootTask.shouldBeVisible(null)) {
-            // The root task is not visible, so ignore this change
-            return;
-        }
-        final RecentsAnimationController controller =
-                mWindowManager.getRecentsAnimationController();
-        if (controller == null) {
-            return;
-        }
-
-        // We defer canceling the recents animation until the next app transition in the following
-        // cases:
-        // 1) The next launching task is not being animated by the recents animation
-        // 2) The next task is home activity. (i.e. pressing home key to back home in recents).
-        if ((!controller.isAnimatingTask(rootTask.getTopMostTask())
-                || controller.isTargetApp(rootTask.getTopNonFinishingActivity()))
-                && controller.shouldDeferCancelUntilNextTransition()) {
-            // Always prepare an app transition since we rely on the transition callbacks to cleanup
-            mWindowManager.prepareAppTransitionNone();
-            controller.setCancelOnNextTransitionStart();
-        }
-    }
-
     private void startRecentsActivityInBackground(String reason) {
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchActivityType(mTargetActivityType);
@@ -492,26 +160,6 @@
     }
 
     /**
-     * Called only when the animation should be canceled prior to starting.
-     */
-    static void notifyAnimationCancelBeforeStart(IRecentsAnimationRunner recentsAnimationRunner) {
-        try {
-            recentsAnimationRunner.onAnimationCanceled(null /* taskIds */,
-                    null /* taskSnapshots */);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to cancel recents animation before start", e);
-        }
-    }
-
-    /**
-     * @return The top root task that is not always-on-top.
-     */
-    private Task getTopNonAlwaysOnTopRootTask() {
-        return mDefaultTaskDisplayArea.getRootTask(task ->
-                !task.getWindowConfiguration().isAlwaysOnTop());
-    }
-
-    /**
      * @return the top activity in the {@param targetRootTask} matching the {@param component},
      * or just the top activity of the top task if no task matches the component.
      */
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
deleted file mode 100644
index 6f94713..0000000
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ /dev/null
@@ -1,1382 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.RemoteAnimationTarget.MODE_CLOSING;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
-import static com.android.server.wm.AnimationAdapterProto.REMOTE;
-import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.app.WindowConfiguration;
-import android.graphics.GraphicBuffer;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder.DeathRecipient;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.util.proto.ProtoOutputStream;
-import android.view.IRecentsAnimationController;
-import android.view.IRecentsAnimationRunner;
-import android.view.InputWindowHandle;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.SurfaceSession;
-import android.view.WindowInsets.Type;
-import android.window.PictureInPictureSurfaceTransaction;
-import android.window.TaskSnapshot;
-import android.window.WindowAnimationState;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.IResultReceiver;
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.LocalServices;
-import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-import com.android.server.wm.utils.InsetUtils;
-
-import com.google.android.collect.Sets;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.stream.Collectors;
-
-/**
- * Controls a single instance of the remote driven recents animation. In particular, this allows
- * the calling SystemUI to animate the visible task windows as a part of the transition. The remote
- * runner is provided an animation controller which allows it to take screenshots and to notify
- * window manager when the animation is completed. In addition, window manager may also notify the
- * app if it requires the animation to be canceled at any time (ie. due to timeout, etc.)
- */
-public class RecentsAnimationController implements DeathRecipient {
-    private static final String TAG = RecentsAnimationController.class.getSimpleName();
-    private static final long FAILSAFE_DELAY = 1000;
-
-    // Constant for a yet-to-be-calculated {@link RemoteAnimationTarget#Mode} state
-    private static final int MODE_UNKNOWN = -1;
-
-    public static final int REORDER_KEEP_IN_PLACE = 0;
-    public static final int REORDER_MOVE_TO_TOP = 1;
-    public static final int REORDER_MOVE_TO_ORIGINAL_POSITION = 2;
-
-    @IntDef(prefix = { "REORDER_MODE_" }, value = {
-            REORDER_KEEP_IN_PLACE,
-            REORDER_MOVE_TO_TOP,
-            REORDER_MOVE_TO_ORIGINAL_POSITION
-    })
-    public @interface ReorderMode {}
-
-    private final WindowManagerService mService;
-    @VisibleForTesting
-    final StatusBarManagerInternal mStatusBar;
-    private IRecentsAnimationRunner mRunner;
-    private final RecentsAnimationCallbacks mCallbacks;
-    private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
-    private final IntArray mPendingNewTaskTargets = new IntArray(0);
-
-    private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
-            new ArrayList<>();
-    private final int mDisplayId;
-    private boolean mWillFinishToHome = false;
-    private final Runnable mFailsafeRunnable = this::onFailsafe;
-
-    // The recents component app token that is shown behind the visible tasks
-    private ActivityRecord mTargetActivityRecord;
-    private DisplayContent mDisplayContent;
-    private int mTargetActivityType;
-
-    // We start the RecentsAnimationController in a pending-start state since we need to wait for
-    // the wallpaper/activity to draw before we can give control to the handler to start animating
-    // the visible task surfaces
-    private boolean mPendingStart = true;
-
-    // Set when the animation has been canceled
-    private boolean mCanceled;
-
-    // Whether or not the input consumer is enabled. The input consumer must be both registered and
-    // enabled for it to start intercepting touch events.
-    private boolean mInputConsumerEnabled;
-
-    private final Rect mTmpRect = new Rect();
-
-    private boolean mLinkedToDeathOfRunner;
-
-    // Whether to try to defer canceling from a root task order change until the next transition
-    private boolean mRequestDeferCancelUntilNextTransition;
-    // Whether to actually defer canceling until the next transition
-    private boolean mCancelOnNextTransitionStart;
-    // Whether to take a screenshot when handling a deferred cancel
-    private boolean mCancelDeferredWithScreenshot;
-    // The reorder mode to apply after the cleanupScreenshot() callback
-    private int mPendingCancelWithScreenshotReorderMode = REORDER_MOVE_TO_ORIGINAL_POSITION;
-
-    @VisibleForTesting
-    boolean mIsAddingTaskToTargets;
-    private boolean mNavigationBarAttachedToApp;
-    private ActivityRecord mNavBarAttachedApp;
-
-    private final ArrayList<RemoteAnimationTarget> mPendingTaskAppears = new ArrayList<>();
-
-    /**
-     * An app transition listener to cancel the recents animation only after the app transition
-     * starts or is canceled.
-     */
-    final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
-        @Override
-        public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
-                long statusBarAnimationDuration) {
-            continueDeferredCancel();
-            return 0;
-        }
-
-        @Override
-        public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
-            continueDeferredCancel();
-        }
-
-        private void continueDeferredCancel() {
-            mDisplayContent.mAppTransition.unregisterListener(this);
-            if (mCanceled) {
-                return;
-            }
-
-            if (mCancelOnNextTransitionStart) {
-                mCancelOnNextTransitionStart = false;
-                cancelAnimationWithScreenshot(mCancelDeferredWithScreenshot);
-            }
-        }
-    };
-
-    public interface RecentsAnimationCallbacks {
-        /** Callback when recents animation is finished. */
-        void onAnimationFinished(@ReorderMode int reorderMode, boolean sendUserLeaveHint);
-    }
-
-    private final IRecentsAnimationController mController =
-            new IRecentsAnimationController.Stub() {
-
-        @Override
-        public TaskSnapshot screenshotTask(int taskId) {
-            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                    "screenshotTask(%d): mCanceled=%b", taskId, mCanceled);
-            final long token = Binder.clearCallingIdentity();
-            try {
-                synchronized (mService.getWindowManagerLock()) {
-                    if (mCanceled) {
-                        return null;
-                    }
-                    for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-                        final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
-                        final Task task = adapter.mTask;
-                        if (task.mTaskId == taskId) {
-                            final TaskSnapshotController snapshotController =
-                                    mService.mTaskSnapshotController;
-                            final ArraySet<Task> tasks = Sets.newArraySet(task);
-                            snapshotController.snapshotTasks(tasks);
-                            snapshotController.addSkipClosingAppSnapshotTasks(tasks);
-                            return snapshotController.getSnapshot(taskId, task.mUserId,
-                                    false /* restoreFromDisk */, false /* isLowResolution */);
-                        }
-                    }
-                    return null;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void setFinishTaskTransaction(int taskId,
-                PictureInPictureSurfaceTransaction finishTransaction,
-                SurfaceControl overlay) {
-            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                    "setFinishTaskTransaction(%d): transaction=%s", taskId, finishTransaction);
-            final long token = Binder.clearCallingIdentity();
-            try {
-                synchronized (mService.getWindowManagerLock()) {
-                    for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-                        final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
-                        if (taskAdapter.mTask.mTaskId == taskId) {
-                            taskAdapter.mFinishTransaction = finishTransaction;
-                            taskAdapter.mFinishOverlay = overlay;
-                            break;
-                        }
-                    }
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void finish(boolean moveHomeToTop, boolean sendUserLeaveHint,
-                IResultReceiver finishCb) {
-            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                    "finish(%b): mCanceled=%b", moveHomeToTop, mCanceled);
-            final long token = Binder.clearCallingIdentity();
-            try {
-                // Note, the callback will handle its own synchronization, do not lock on WM lock
-                // prior to calling the callback
-                mCallbacks.onAnimationFinished(moveHomeToTop
-                        ? REORDER_MOVE_TO_TOP
-                        : REORDER_MOVE_TO_ORIGINAL_POSITION, sendUserLeaveHint);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-            if (finishCb != null) {
-                try {
-                    finishCb.send(0, new Bundle());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to report animation finished", e);
-                }
-            }
-        }
-
-        @Override
-        public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars)
-                throws RemoteException {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                synchronized (mService.getWindowManagerLock()) {
-                    for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-                        final Task task = mPendingAnimations.get(i).mTask;
-                        if (task.getActivityType() != mTargetActivityType) {
-                            task.setCanAffectSystemUiFlags(behindSystemBars);
-                        }
-                    }
-                    InputMethodManagerInternal.get().maybeFinishStylusHandwriting();
-                    mService.mWindowPlacerLocked.requestTraversal();
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void setInputConsumerEnabled(boolean enabled) {
-            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                    "setInputConsumerEnabled(%s): mCanceled=%b", enabled, mCanceled);
-            final long token = Binder.clearCallingIdentity();
-            try {
-                synchronized (mService.getWindowManagerLock()) {
-                    if (mCanceled) {
-                        return;
-                    }
-                    mInputConsumerEnabled = enabled;
-                    final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
-                    inputMonitor.updateInputWindowsLw(true /*force*/);
-                    mService.scheduleAnimationLocked();
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
-            synchronized (mService.mGlobalLock) {
-                setDeferredCancel(defer, screenshot);
-            }
-        }
-
-        @Override
-        public void cleanupScreenshot() {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                // Note, the callback will handle its own synchronization, do not lock on WM lock
-                // prior to calling the callback
-                continueDeferredCancelAnimation();
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void setWillFinishToHome(boolean willFinishToHome) {
-            synchronized (mService.getWindowManagerLock()) {
-                RecentsAnimationController.this.setWillFinishToHome(willFinishToHome);
-            }
-        }
-
-        @Override
-        public boolean removeTask(int taskId) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                synchronized (mService.getWindowManagerLock()) {
-                    return removeTaskInternal(taskId);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void detachNavigationBarFromApp(boolean moveHomeToTop) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                synchronized (mService.getWindowManagerLock()) {
-                    restoreNavigationBarFromApp(
-                            moveHomeToTop || mIsAddingTaskToTargets /* animate */);
-                    mService.mWindowPlacerLocked.requestTraversal();
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void animateNavigationBarToApp(long duration) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                synchronized (mService.getWindowManagerLock()) {
-                    animateNavigationBarForAppLaunch(duration);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
-        public void handOffAnimation(
-                RemoteAnimationTarget[] targets, WindowAnimationState[] states) {
-            // unused legacy implementation
-        }
-    };
-
-    /**
-     * @param remoteAnimationRunner The remote runner which should be notified when the animation is
-     *                              ready to start or has been canceled
-     * @param callbacks Callbacks to be made when the animation finishes
-     */
-    RecentsAnimationController(WindowManagerService service,
-            IRecentsAnimationRunner remoteAnimationRunner, RecentsAnimationCallbacks callbacks,
-            int displayId) {
-        mService = service;
-        mRunner = remoteAnimationRunner;
-        mCallbacks = callbacks;
-        mDisplayId = displayId;
-        mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
-        mDisplayContent = service.mRoot.getDisplayContent(displayId);
-    }
-
-    /**
-     * Initializes the recents animation controller. This is a separate call from the constructor
-     * because it may call cancelAnimation() which needs to properly clean up the controller
-     * in the window manager.
-     */
-    public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds,
-            ActivityRecord targetActivity) {
-        mTargetActivityType = targetActivityType;
-        mDisplayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
-
-        // Make leashes for each of the visible/target tasks and add it to the recents animation to
-        // be started
-        // TODO(b/153090560): Support Recents on multiple task display areas
-        final ArrayList<Task> visibleTasks = mDisplayContent.getDefaultTaskDisplayArea()
-                .getVisibleTasks();
-        final Task targetRootTask = mDisplayContent.getDefaultTaskDisplayArea()
-                .getRootTask(WINDOWING_MODE_UNDEFINED, targetActivityType);
-        if (targetRootTask != null) {
-            targetRootTask.forAllLeafTasks(t -> {
-                if (!visibleTasks.contains(t)) {
-                    visibleTasks.add(t);
-                }
-            }, true /* traverseTopToBottom */);
-        }
-
-        final int taskCount = visibleTasks.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            final Task task = visibleTasks.get(i);
-            if (skipAnimation(task)) {
-                continue;
-            }
-            addAnimation(task, !recentTaskIds.get(task.mTaskId), false /* hidden */,
-                    (type, anim) -> task.forAllWindows(win -> {
-                        win.onAnimationFinished(type, anim);
-                    }, true /* traverseTopToBottom */));
-        }
-
-        // Skip the animation if there is nothing to animate
-        if (mPendingAnimations.isEmpty()) {
-            cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-noVisibleTasks");
-            return;
-        }
-
-        try {
-            linkToDeathOfRunner();
-        } catch (RemoteException e) {
-            cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "initialize-failedToLinkToDeath");
-            return;
-        }
-
-        attachNavigationBarToApp();
-
-        // Adjust the wallpaper visibility for the showing target activity
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                "setHomeApp(%s)", targetActivity.getName());
-        mTargetActivityRecord = targetActivity;
-        if (targetActivity.windowsCanBeWallpaperTarget()) {
-            mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-            mDisplayContent.setLayoutNeeded();
-        }
-
-        mService.mWindowPlacerLocked.performSurfacePlacement();
-
-        mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity);
-
-        // Notify that the animation has started
-        if (mStatusBar != null) {
-            mStatusBar.onRecentsAnimationStateChanged(true /* running */);
-        }
-    }
-
-    /**
-     * Return whether the given window should still be considered interesting for the all-drawn
-     * state.  This is only interesting for the target app, which may have child windows that are
-     * not actually visible and should not be considered interesting and waited upon.
-     */
-    protected boolean isInterestingForAllDrawn(WindowState window) {
-        if (isTargetApp(window.getActivityRecord())) {
-            if (window.getWindowType() != TYPE_BASE_APPLICATION
-                    && window.getAttrs().alpha == 0f) {
-                // If there is a cihld window that is alpha 0, then ignore that window
-                return false;
-            }
-        }
-        // By default all windows are still interesting for all drawn purposes
-        return true;
-    }
-
-    /**
-     * Whether a task should be filtered from the recents animation. This can be true for tasks
-     * being displayed outside of recents.
-     */
-    private boolean skipAnimation(Task task) {
-        final WindowConfiguration config = task.getWindowConfiguration();
-        return task.isAlwaysOnTop() || config.tasksAreFloating();
-    }
-
-    @VisibleForTesting
-    TaskAnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
-        return addAnimation(task, isRecentTaskInvisible, false /* hidden */,
-                null /* finishedCallback */);
-    }
-
-    @VisibleForTesting
-    TaskAnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible, boolean hidden,
-            OnAnimationFinishedCallback finishedCallback) {
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName());
-        final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
-                isRecentTaskInvisible);
-        task.startAnimation(task.getPendingTransaction(), taskAdapter, hidden,
-                ANIMATION_TYPE_RECENTS, finishedCallback);
-        task.commitPendingTransaction();
-        mPendingAnimations.add(taskAdapter);
-        return taskAdapter;
-    }
-
-    @VisibleForTesting
-    void removeAnimation(TaskAnimationAdapter taskAdapter) {
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                "removeAnimation(%d)", taskAdapter.mTask.mTaskId);
-        taskAdapter.onRemove();
-        mPendingAnimations.remove(taskAdapter);
-    }
-
-    @VisibleForTesting
-    void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) {
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "removeWallpaperAnimation()");
-        wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(
-                wallpaperAdapter.getLastAnimationType(), wallpaperAdapter);
-        mPendingWallpaperAnimations.remove(wallpaperAdapter);
-    }
-
-    void startAnimation() {
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                "startAnimation(): mPendingStart=%b mCanceled=%b", mPendingStart, mCanceled);
-        if (!mPendingStart || mCanceled) {
-            // Skip starting if we've already started or canceled the animation
-            return;
-        }
-        try {
-            // Create the app targets
-            final RemoteAnimationTarget[] appTargets = createAppAnimations();
-
-            // Skip the animation if there is nothing to animate
-            if (appTargets.length == 0) {
-                cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows");
-                return;
-            }
-
-            // Create the wallpaper targets
-            final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
-
-            mPendingStart = false;
-
-            final Rect contentInsets;
-            final WindowState targetAppMainWindow = getTargetAppMainWindow();
-            if (targetAppMainWindow != null) {
-                contentInsets = targetAppMainWindow
-                        .getInsetsStateWithVisibilityOverride()
-                        .calculateInsets(mTargetActivityRecord.getBounds(), Type.systemBars(),
-                                false /* ignoreVisibility */).toRect();
-            } else {
-                // If the window for the activity had not yet been created, use the display insets.
-                mService.getStableInsets(mDisplayId, mTmpRect);
-                contentInsets = mTmpRect;
-            }
-            mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets,
-                    null, new Bundle());
-            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                    "startAnimation(): Notify animation start: %s",
-                    mPendingAnimations.stream()
-                            .map(anim->anim.mTask.mTaskId).collect(Collectors.toList()));
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to start recents animation", e);
-        }
-
-        if (mTargetActivityRecord != null) {
-            final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(1);
-            reasons.put(mTargetActivityRecord, APP_TRANSITION_RECENTS_ANIM);
-            mService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
-                    .notifyTransitionStarting(reasons);
-        }
-    }
-
-    boolean isNavigationBarAttachedToApp() {
-        return mNavigationBarAttachedToApp;
-    }
-
-    @VisibleForTesting
-    WindowState getNavigationBarWindow() {
-        return mDisplayContent.getDisplayPolicy().getNavigationBar();
-    }
-
-    private void attachNavigationBarToApp() {
-        if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
-                // Skip the case where the nav bar is controlled by fade rotation.
-                || mDisplayContent.getAsyncRotationController() != null) {
-            return;
-        }
-        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
-            final Task task = adapter.mTask;
-            if (task.isActivityTypeHomeOrRecents()) {
-                continue;
-            }
-            mNavBarAttachedApp = task.getTopVisibleActivity();
-            break;
-        }
-
-        final WindowState navWindow = getNavigationBarWindow();
-        if (mNavBarAttachedApp == null || navWindow == null || navWindow.mToken == null) {
-            return;
-        }
-        mNavigationBarAttachedToApp = true;
-        navWindow.mToken.cancelAnimation();
-        final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
-        final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
-        navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top);
-        t.reparent(navSurfaceControl, mNavBarAttachedApp.getSurfaceControl());
-        t.show(navSurfaceControl);
-
-        final WindowContainer imeContainer = mDisplayContent.getImeContainer();
-        if (imeContainer.isVisible()) {
-            t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1);
-        } else {
-            // Place the nav bar on top of anything else in the top activity.
-            t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
-        }
-        if (mStatusBar != null) {
-            mStatusBar.setNavigationBarLumaSamplingEnabled(mDisplayId, false);
-        }
-    }
-
-    @VisibleForTesting
-    void restoreNavigationBarFromApp(boolean animate) {
-        if (!mNavigationBarAttachedToApp) {
-            return;
-        }
-        mNavigationBarAttachedToApp = false;
-
-        if (mStatusBar != null) {
-            mStatusBar.setNavigationBarLumaSamplingEnabled(mDisplayId, true);
-        }
-
-        final WindowState navWindow = getNavigationBarWindow();
-        if (navWindow == null) {
-            return;
-        }
-        navWindow.setSurfaceTranslationY(0);
-
-        final WindowToken navToken = navWindow.mToken;
-        if (navToken == null) {
-            return;
-        }
-        final SurfaceControl.Transaction t = mDisplayContent.getPendingTransaction();
-        final WindowContainer parent = navToken.getParent();
-        t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer());
-
-        if (animate) {
-            final NavBarFadeAnimationController controller =
-                        new NavBarFadeAnimationController(mDisplayContent);
-            controller.fadeWindowToken(true);
-        } else {
-            // Reparent the SurfaceControl of nav bar token back.
-            t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
-        }
-    }
-
-    void animateNavigationBarForAppLaunch(long duration) {
-        if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
-                // Skip the case where the nav bar is controlled by fade rotation.
-                || mDisplayContent.getAsyncRotationController() != null
-                || mNavigationBarAttachedToApp
-                || mNavBarAttachedApp == null) {
-            return;
-        }
-
-        final NavBarFadeAnimationController controller =
-                new NavBarFadeAnimationController(mDisplayContent);
-        controller.fadeOutAndInSequentially(duration, null /* fadeOutParent */,
-                mNavBarAttachedApp.getSurfaceControl());
-    }
-
-    void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) {
-        if (mRunner != null) {
-            mIsAddingTaskToTargets = task != null;
-            mNavBarAttachedApp = task == null ? null : task.getTopVisibleActivity();
-            // No need to send task appeared when the task target already exists, or when the
-            // task is being managed as a multi-window mode outside of recents (e.g. bubbles).
-            if (isAnimatingTask(task) || skipAnimation(task)) {
-                return;
-            }
-            collectTaskRemoteAnimations(task, MODE_OPENING, finishedCallback);
-        }
-    }
-
-    void sendTasksAppeared() {
-        if (mPendingTaskAppears.isEmpty() || mRunner == null) return;
-        try {
-            final RemoteAnimationTarget[] targets = mPendingTaskAppears.toArray(
-                    new RemoteAnimationTarget[0]);
-            mRunner.onTasksAppeared(targets);
-            mPendingTaskAppears.clear();
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to report task appeared", e);
-        }
-    }
-
-    private void collectTaskRemoteAnimations(Task task, int mode,
-            OnAnimationFinishedCallback finishedCallback) {
-        final SparseBooleanArray recentTaskIds =
-                mService.mAtmService.getRecentTasks().getRecentTaskIds();
-
-        // The target must be built off the root task (the leaf task surface would be cropped
-        // within the root surface). However, recents only tracks leaf task ids, so we'll traverse
-        // and create animation target for all visible leaf tasks.
-        task.forAllLeafTasks(leafTask -> {
-            if (!leafTask.shouldBeVisible(null /* starting */)) {
-                return;
-            }
-            final int taskId = leafTask.mTaskId;
-            TaskAnimationAdapter adapter = addAnimation(leafTask,
-                    !recentTaskIds.get(taskId), true /* hidden */, finishedCallback);
-            mPendingNewTaskTargets.add(taskId);
-            final RemoteAnimationTarget target =
-                    adapter.createRemoteAnimationTarget(taskId, mode);
-            if (target != null) {
-                mPendingTaskAppears.add(target);
-                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                        "collectTaskRemoteAnimations, target: %s", target);
-            }
-        }, false /* traverseTopToBottom */);
-    }
-
-    private boolean removeTaskInternal(int taskId) {
-        boolean result = false;
-        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            // Only allows when task target has became visible to user, to prevent
-            // the flickering during remove animation and task visible.
-            final TaskAnimationAdapter target = mPendingAnimations.get(i);
-            if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) {
-                removeAnimation(target);
-                final int taskIndex = mPendingNewTaskTargets.indexOf(taskId);
-                if (taskIndex != -1) {
-                    mPendingNewTaskTargets.remove(taskIndex);
-                }
-                result = true;
-                break;
-            }
-        }
-        return result;
-    }
-
-    private RemoteAnimationTarget[] createAppAnimations() {
-        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
-        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
-            final RemoteAnimationTarget target =
-                    taskAdapter.createRemoteAnimationTarget(INVALID_TASK_ID, MODE_UNKNOWN);
-            if (target != null) {
-                targets.add(target);
-            } else {
-                removeAnimation(taskAdapter);
-            }
-        }
-        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
-    }
-
-    private RemoteAnimationTarget[] createWallpaperAnimations() {
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()");
-        return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, 0L, 0L,
-                adapter -> {
-                    synchronized (mService.mGlobalLock) {
-                        // If the wallpaper animation is canceled, continue with the recents
-                        // animation
-                        mPendingWallpaperAnimations.remove(adapter);
-                    }
-                }, mPendingWallpaperAnimations);
-    }
-
-    void forceCancelAnimation(@ReorderMode int reorderMode, String reason) {
-        if (!mCanceled) {
-            cancelAnimation(reorderMode, reason);
-        } else {
-            continueDeferredCancelAnimation();
-        }
-    }
-
-    void cancelAnimation(@ReorderMode int reorderMode, String reason) {
-        cancelAnimation(reorderMode, false /*screenshot */, reason);
-    }
-
-    void cancelAnimationWithScreenshot(boolean screenshot) {
-        cancelAnimation(REORDER_KEEP_IN_PLACE, screenshot, "rootTaskOrderChanged");
-    }
-
-    /**
-     * Cancels the running animation when starting home, providing a snapshot for the runner to
-     * properly handle the cancellation. This call uses the provided hint to determine how to
-     * finish the animation.
-     */
-    public void cancelAnimationForHomeStart() {
-        final int reorderMode = mTargetActivityType == ACTIVITY_TYPE_HOME && mWillFinishToHome
-                ? REORDER_MOVE_TO_TOP
-                : REORDER_KEEP_IN_PLACE;
-        cancelAnimation(reorderMode, true /* screenshot */, "cancelAnimationForHomeStart");
-    }
-
-    /**
-     * Cancels the running animation when there is a display change, providing a snapshot for the
-     * runner to properly handle the cancellation. This call uses the provided hint to determine
-     * how to finish the animation.
-     */
-    public void cancelAnimationForDisplayChange() {
-        cancelAnimation(mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
-                true /* screenshot */, "cancelAnimationForDisplayChange");
-    }
-
-    private void cancelAnimation(@ReorderMode int reorderMode, boolean screenshot, String reason) {
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
-        synchronized (mService.getWindowManagerLock()) {
-            if (mCanceled) {
-                // We've already canceled the animation
-                return;
-            }
-            mService.mH.removeCallbacks(mFailsafeRunnable);
-            mCanceled = true;
-
-            if (screenshot && !mPendingAnimations.isEmpty()) {
-                final ArrayMap<Task, TaskSnapshot> snapshotMap = screenshotRecentTasks();
-                mPendingCancelWithScreenshotReorderMode = reorderMode;
-
-                if (!snapshotMap.isEmpty()) {
-                    try {
-                        int[] taskIds = new int[snapshotMap.size()];
-                        TaskSnapshot[] snapshots = new TaskSnapshot[snapshotMap.size()];
-                        for (int i = snapshotMap.size() - 1; i >= 0; i--) {
-                            taskIds[i] = snapshotMap.keyAt(i).mTaskId;
-                            snapshots[i] = snapshotMap.valueAt(i);
-                        }
-                        mRunner.onAnimationCanceled(taskIds, snapshots);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Failed to cancel recents animation", e);
-                    }
-                    // Schedule a new failsafe for if the runner doesn't clean up the screenshot
-                    scheduleFailsafe();
-                    return;
-                }
-                // Fallback to a normal cancel since we couldn't screenshot
-            }
-
-            // Notify the runner and clean up the animation immediately
-            // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls
-            // to the runner if we this actually triggers cancel twice on the caller
-            try {
-                mRunner.onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to cancel recents animation", e);
-            }
-            mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
-        }
-    }
-
-    @VisibleForTesting
-    void continueDeferredCancelAnimation() {
-        mCallbacks.onAnimationFinished(mPendingCancelWithScreenshotReorderMode,
-                false /* sendUserLeaveHint */);
-    }
-
-    @VisibleForTesting
-    void setWillFinishToHome(boolean willFinishToHome) {
-        mWillFinishToHome = willFinishToHome;
-    }
-
-    /**
-     * Cancel recents animation when the next app transition starts.
-     * <p>
-     * When we cancel the recents animation due to a root task order change, we can't just cancel it
-     * immediately as it would lead to a flicker in Launcher if we just remove the task from the
-     * leash. Instead we screenshot the previous task and replace the child of the leash with the
-     * screenshot, so that Launcher can still control the leash lifecycle & make the next app
-     * transition animate smoothly without flickering.
-     */
-    void setCancelOnNextTransitionStart() {
-        mCancelOnNextTransitionStart = true;
-    }
-
-    /**
-     * Requests that we attempt to defer the cancel until the next app transition if we are
-     * canceling from a root task order change.  If {@param screenshot} is specified, then the
-     * system will replace the contents of the leash with a screenshot, which must be cleaned up
-     * when the runner calls cleanUpScreenshot().
-     */
-    void setDeferredCancel(boolean defer, boolean screenshot) {
-        mRequestDeferCancelUntilNextTransition = defer;
-        mCancelDeferredWithScreenshot = screenshot;
-    }
-
-    /**
-     * @return Whether we should defer the cancel from a root task order change until the next app
-     * transition.
-     */
-    boolean shouldDeferCancelUntilNextTransition() {
-        return mRequestDeferCancelUntilNextTransition;
-    }
-
-    /**
-     * @return Whether we should both defer the cancel from a root task order change until the next
-     * app transition, and also that the deferred cancel should replace the contents of the leash
-     * with a screenshot.
-     */
-    boolean shouldDeferCancelWithScreenshot() {
-        return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot;
-    }
-
-    private ArrayMap<Task, TaskSnapshot> screenshotRecentTasks() {
-        final TaskSnapshotController snapshotController = mService.mTaskSnapshotController;
-        final ArrayMap<Task, TaskSnapshot> snapshotMap = new ArrayMap<>();
-        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
-            final Task task = adapter.mTask;
-            if (task.isActivityTypeHome()) continue;
-            snapshotController.recordSnapshot(task);
-            final TaskSnapshot snapshot = snapshotController.getSnapshot(task.mTaskId, task.mUserId,
-                    false /* restoreFromDisk */, false /* isLowResolution */);
-            if (snapshot != null) {
-                snapshotMap.put(task, snapshot);
-                // Defer until the runner calls back to cleanupScreenshot()
-                adapter.setSnapshotOverlay(snapshot);
-            }
-        }
-        snapshotController.addSkipClosingAppSnapshotTasks(snapshotMap.keySet());
-        return snapshotMap;
-    }
-
-    void cleanupAnimation(@ReorderMode int reorderMode) {
-        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                        "cleanupAnimation(): Notify animation finished mPendingAnimations=%d "
-                                + "reorderMode=%d",
-                        mPendingAnimations.size(), reorderMode);
-        if (reorderMode != REORDER_MOVE_TO_ORIGINAL_POSITION
-                && mTargetActivityRecord != mDisplayContent.topRunningActivity()) {
-            // Notify the state at the beginning because the removeAnimation may notify the
-            // transition is finished. This is a signal that there will be a next transition.
-            mDisplayContent.mFixedRotationTransitionListener.notifyRecentsWillBeTop();
-        }
-        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
-            if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
-                taskAdapter.mTask.dontAnimateDimExit();
-            }
-            removeAnimation(taskAdapter);
-            taskAdapter.onCleanup();
-        }
-        // Should already be empty, but clean-up pending task-appears in-case they weren't sent.
-        mPendingNewTaskTargets.clear();
-        mPendingTaskAppears.clear();
-
-        for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
-            final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i);
-            removeWallpaperAnimation(wallpaperAdapter);
-        }
-
-        restoreNavigationBarFromApp(
-                reorderMode == REORDER_MOVE_TO_TOP || mIsAddingTaskToTargets /* animate */);
-
-        // Clear any pending failsafe runnables
-        mService.mH.removeCallbacks(mFailsafeRunnable);
-        mDisplayContent.mAppTransition.unregisterListener(mAppTransitionListener);
-
-        // Clear references to the runner
-        unlinkToDeathOfRunner();
-        mRunner = null;
-        mCanceled = true;
-
-        // Restore IME icon only when moving the original app task to front from recents, in case
-        // IME icon may missing if the moving task has already been the current focused task.
-        if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION && !mIsAddingTaskToTargets) {
-            InputMethodManagerInternal.get().updateImeWindowStatus(
-                    false /* disableImeIcon */, mDisplayId);
-        }
-
-        // Update the input windows after the animation is complete
-        final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
-        inputMonitor.updateInputWindowsLw(true /*force*/);
-
-        // We have deferred all notifications to the target app as a part of the recents animation,
-        // so if we are actually transitioning there, notify again here
-        if (mTargetActivityRecord != null) {
-            if (reorderMode == REORDER_MOVE_TO_TOP || reorderMode == REORDER_KEEP_IN_PLACE) {
-                mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(
-                        mTargetActivityRecord.token);
-            }
-        }
-        mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
-
-        // Notify that the animation has ended
-        if (mStatusBar != null) {
-            mStatusBar.onRecentsAnimationStateChanged(false /* running */);
-        }
-    }
-
-    void scheduleFailsafe() {
-        mService.mH.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY);
-    }
-
-    void onFailsafe() {
-        forceCancelAnimation(
-                mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
-                "onFailsafe");
-    }
-
-    private void linkToDeathOfRunner() throws RemoteException {
-        if (!mLinkedToDeathOfRunner) {
-            mRunner.asBinder().linkToDeath(this, 0);
-            mLinkedToDeathOfRunner = true;
-        }
-    }
-
-    private void unlinkToDeathOfRunner() {
-        if (mLinkedToDeathOfRunner) {
-            mRunner.asBinder().unlinkToDeath(this, 0);
-            mLinkedToDeathOfRunner = false;
-        }
-    }
-
-    @Override
-    public void binderDied() {
-        forceCancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied");
-
-        synchronized (mService.getWindowManagerLock()) {
-            // Clear associated input consumers on runner death
-            final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
-            final InputConsumerImpl consumer = inputMonitor.getInputConsumer(
-                    INPUT_CONSUMER_RECENTS_ANIMATION);
-            if (consumer != null) {
-                inputMonitor.destroyInputConsumer(consumer.mToken);
-            }
-        }
-    }
-
-    void checkAnimationReady(WallpaperController wallpaperController) {
-        if (mPendingStart) {
-            final boolean wallpaperReady = !isTargetOverWallpaper()
-                    || (wallpaperController.getWallpaperTarget() != null
-                            && wallpaperController.wallpaperTransitionReady());
-            if (wallpaperReady) {
-                mService.getRecentsAnimationController().startAnimation();
-            }
-        }
-    }
-
-    boolean isWallpaperVisible(WindowState w) {
-        return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION &&
-                ((w.mActivityRecord != null && mTargetActivityRecord == w.mActivityRecord)
-                        || isAnimatingTask(w.getTask()))
-                && isTargetOverWallpaper() && w.isOnScreen();
-    }
-
-    /**
-     * @return Whether to use the input consumer to override app input to route home/recents.
-     */
-    boolean shouldApplyInputConsumer(ActivityRecord activity) {
-        // Only apply the input consumer if it is enabled, it is not the target (home/recents)
-        // being revealed with the transition, and we are actively animating the app as a part of
-        // the animation
-        return mInputConsumerEnabled && activity != null
-                && !isTargetApp(activity) && isAnimatingApp(activity);
-    }
-
-    boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle) {
-        // Update the input consumer touchable region to match the target app main window
-        final WindowState targetAppMainWindow = getTargetAppMainWindow();
-        if (targetAppMainWindow != null) {
-            targetAppMainWindow.getBounds(mTmpRect);
-            inputWindowHandle.touchableRegion.set(mTmpRect);
-            return true;
-        }
-        return false;
-    }
-
-    boolean isTargetApp(ActivityRecord activity) {
-        return mTargetActivityRecord != null && activity == mTargetActivityRecord;
-    }
-
-    private boolean isTargetOverWallpaper() {
-        if (mTargetActivityRecord == null) {
-            return false;
-        }
-        return mTargetActivityRecord.windowsCanBeWallpaperTarget();
-    }
-
-    WindowState getTargetAppMainWindow() {
-        if (mTargetActivityRecord == null) {
-            return null;
-        }
-        return mTargetActivityRecord.findMainWindow();
-    }
-
-    DisplayArea getTargetAppDisplayArea() {
-        if (mTargetActivityRecord == null) {
-            return null;
-        }
-        return mTargetActivityRecord.getDisplayArea();
-    }
-
-    boolean isAnimatingTask(Task task) {
-        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            if (task == mPendingAnimations.get(i).mTask) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    boolean isAnimatingWallpaper(WallpaperWindowToken token) {
-        for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
-            if (token == mPendingWallpaperAnimations.get(i).getToken()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean isAnimatingApp(ActivityRecord activity) {
-        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-            if (activity.isDescendantOf(mPendingAnimations.get(i).mTask)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    boolean shouldIgnoreForAccessibility(WindowState windowState) {
-        final Task task = windowState.getTask();
-        return task != null && isAnimatingTask(task) && !isTargetApp(windowState.mActivityRecord);
-    }
-
-    /**
-     * If the animation target ActivityRecord has a fixed rotation ({@link
-     * WindowToken#hasFixedRotationTransform()}, the provided wallpaper will be rotated accordingly.
-     *
-     * This avoids any screen rotation animation when animating to the Recents view.
-     */
-    void linkFixedRotationTransformIfNeeded(@NonNull WindowToken wallpaper) {
-        if (mTargetActivityRecord == null) {
-            return;
-        }
-        wallpaper.linkFixedRotationTransform(mTargetActivityRecord);
-    }
-
-    @VisibleForTesting
-    class TaskAnimationAdapter implements AnimationAdapter {
-
-        private final Task mTask;
-        private SurfaceControl mCapturedLeash;
-        private OnAnimationFinishedCallback mCapturedFinishCallback;
-        private @AnimationType int mLastAnimationType;
-        private final boolean mIsRecentTaskInvisible;
-        private RemoteAnimationTarget mTarget;
-        private final Rect mBounds = new Rect();
-        // The bounds of the target relative to its parent.
-        private final Rect mLocalBounds = new Rect();
-        // The final surface transaction when animation is finished.
-        private PictureInPictureSurfaceTransaction mFinishTransaction;
-        // An overlay used to mask the content as an app goes into PIP
-        private SurfaceControl mFinishOverlay;
-        // An overlay used for canceling the animation with a screenshot
-        private SurfaceControl mSnapshotOverlay;
-
-        TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible) {
-            mTask = task;
-            mIsRecentTaskInvisible = isRecentTaskInvisible;
-            mBounds.set(mTask.getBounds());
-
-            mLocalBounds.set(mBounds);
-            Point tmpPos = new Point();
-            mTask.getRelativePosition(tmpPos);
-            mLocalBounds.offsetTo(tmpPos.x, tmpPos.y);
-        }
-
-        /**
-         * @param overrideTaskId overrides the target's taskId. It may differ from mTaskId and thus
-         *                       can differ from taskInfo. This mismatch is needed, however, in
-         *                       some cases where we are animating root tasks but need need leaf
-         *                       ids for identification. If this is INVALID (-1), then mTaskId
-         *                       will be used.
-         * @param overrideMode overrides the target's mode. If this is -1, the mode will be
-         *                     calculated relative to going to the target activity (ie. OPENING if
-         *                     this is the target task, CLOSING otherwise).
-         */
-        RemoteAnimationTarget createRemoteAnimationTarget(int overrideTaskId, int overrideMode) {
-            ActivityRecord topApp = mTask.getTopRealVisibleActivity();
-            if (topApp == null) {
-                topApp = mTask.getTopVisibleActivity();
-            }
-            final WindowState mainWindow = topApp != null
-                    ? topApp.findMainWindow()
-                    : null;
-            if (mainWindow == null) {
-                return null;
-            }
-            final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
-                    mBounds, Type.systemBars(), false /* ignoreVisibility */).toRect();
-            InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
-            final int mode = overrideMode != MODE_UNKNOWN
-                    ? overrideMode
-                    : topApp.getActivityType() == mTargetActivityType
-                            ? MODE_OPENING
-                            : MODE_CLOSING;
-            if (overrideTaskId < 0) {
-                overrideTaskId = mTask.mTaskId;
-            }
-            mTarget = new RemoteAnimationTarget(overrideTaskId, mode, mCapturedLeash,
-                    !topApp.fillsParent(), new Rect(),
-                    insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top),
-                    mLocalBounds, mBounds, mTask.getWindowConfiguration(),
-                    mIsRecentTaskInvisible, null, null, mTask.getTaskInfo(),
-                    topApp.checkEnterPictureInPictureAppOpsState());
-
-            final ActivityRecord topActivity = mTask.getTopNonFinishingActivity();
-            if (topActivity != null && topActivity.mStartingData != null
-                    && topActivity.mStartingData.hasImeSurface()) {
-                mTarget.setWillShowImeOnTarget(true);
-            }
-            return mTarget;
-        }
-
-        void setSnapshotOverlay(TaskSnapshot snapshot) {
-            // Create a surface control for the snapshot and reparent it to the leash
-            final HardwareBuffer buffer = snapshot.getHardwareBuffer();
-            if (buffer == null) {
-                return;
-            }
-
-            final SurfaceSession session = new SurfaceSession();
-            mSnapshotOverlay = mService.mSurfaceControlFactory.apply(session)
-                    .setName("RecentTaskScreenshotSurface")
-                    .setCallsite("TaskAnimationAdapter.setSnapshotOverlay")
-                    .setFormat(buffer.getFormat())
-                    .setParent(mCapturedLeash)
-                    .setBLASTLayer()
-                    .build();
-
-            final float scale = 1.0f * mTask.getBounds().width() / buffer.getWidth();
-            mTask.getPendingTransaction()
-                    .setBuffer(mSnapshotOverlay, GraphicBuffer.createFromHardwareBuffer(buffer))
-                    .setColorSpace(mSnapshotOverlay, snapshot.getColorSpace())
-                    .setLayer(mSnapshotOverlay, Integer.MAX_VALUE)
-                    .setMatrix(mSnapshotOverlay, scale, 0, 0, scale)
-                    .show(mSnapshotOverlay)
-                    .apply();
-        }
-
-        void onRemove() {
-            if (mSnapshotOverlay != null) {
-                // Clean up the snapshot overlay if necessary
-                mTask.getPendingTransaction()
-                        .remove(mSnapshotOverlay)
-                        .apply();
-                mSnapshotOverlay = null;
-            }
-            mTask.setCanAffectSystemUiFlags(true);
-            mCapturedFinishCallback.onAnimationFinished(mLastAnimationType, this);
-        }
-
-        void onCleanup() {
-            final Transaction pendingTransaction = mTask.getPendingTransaction();
-            if (mFinishTransaction != null) {
-                // Reparent the overlay
-                if (mFinishOverlay != null) {
-                    pendingTransaction.reparent(mFinishOverlay, mTask.mSurfaceControl);
-                }
-
-                // Transfer the transform from the leash to the task
-                PictureInPictureSurfaceTransaction.apply(mFinishTransaction,
-                        mTask.mSurfaceControl, pendingTransaction);
-                mTask.setLastRecentsAnimationTransaction(mFinishTransaction, mFinishOverlay);
-                if (mDisplayContent.isFixedRotationLaunchingApp(mTargetActivityRecord)) {
-                    // The transaction is needed for position when rotating the display.
-                    mDisplayContent.mPinnedTaskController.setEnterPipTransaction(
-                            mFinishTransaction);
-                }
-                // In the case where we are transferring the transform to the task in preparation
-                // for entering PIP, we disable the task being able to affect sysui flags otherwise
-                // it may cause a flash
-                if (mTask.getActivityType() != mTargetActivityType
-                        && mFinishTransaction.getShouldDisableCanAffectSystemUiFlags()) {
-                    mTask.setCanAffectSystemUiFlags(false);
-                }
-                mFinishTransaction = null;
-                mFinishOverlay = null;
-                pendingTransaction.apply();
-            } else if (!mTask.isAttached()) {
-                // Apply the task's pending transaction in case it is detached and its transaction
-                // is not reachable.
-                pendingTransaction.apply();
-            }
-        }
-
-        @VisibleForTesting
-        public SurfaceControl getSnapshotOverlay() {
-            return mSnapshotOverlay;
-        }
-
-        @Override
-        public boolean getShowWallpaper() {
-            return false;
-        }
-
-        @Override
-        public void startAnimation(SurfaceControl animationLeash, Transaction t,
-                @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
-            // Restore position and root task crop until client has a chance to modify it.
-            t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top);
-            mTmpRect.set(mLocalBounds);
-            mTmpRect.offsetTo(0, 0);
-            t.setWindowCrop(animationLeash, mTmpRect);
-            mCapturedLeash = animationLeash;
-            mCapturedFinishCallback = finishCallback;
-            mLastAnimationType = type;
-        }
-
-        @Override
-        public void onAnimationCancelled(SurfaceControl animationLeash) {
-            // Cancel the animation immediately if any single task animator is canceled
-            cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled");
-        }
-
-        @Override
-        public long getDurationHint() {
-            return 0;
-        }
-
-        @Override
-        public long getStatusBarTransitionsStartTime() {
-            return SystemClock.uptimeMillis();
-        }
-
-        @Override
-        public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix); pw.println("task=" + mTask);
-            if (mTarget != null) {
-                pw.print(prefix); pw.println("Target:");
-                mTarget.dump(pw, prefix + "  ");
-            } else {
-                pw.print(prefix); pw.println("Target: null");
-            }
-            pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
-            pw.println("mLocalBounds=" + mLocalBounds);
-            pw.println("mFinishTransaction=" + mFinishTransaction);
-            pw.println("mBounds=" + mBounds);
-            pw.println("mIsRecentTaskInvisible=" + mIsRecentTaskInvisible);
-        }
-
-        @Override
-        public void dumpDebug(ProtoOutputStream proto) {
-            final long token = proto.start(REMOTE);
-            if (mTarget != null) {
-                mTarget.dumpDebug(proto, TARGET);
-            }
-            proto.end(token);
-        }
-    }
-
-    public void dump(PrintWriter pw, String prefix) {
-        final String innerPrefix = prefix + "  ";
-        pw.print(prefix); pw.println(RecentsAnimationController.class.getSimpleName() + ":");
-        pw.print(innerPrefix); pw.println("mPendingStart=" + mPendingStart);
-        pw.print(innerPrefix); pw.println("mPendingAnimations=" + mPendingAnimations.size());
-        pw.print(innerPrefix); pw.println("mCanceled=" + mCanceled);
-        pw.print(innerPrefix); pw.println("mInputConsumerEnabled=" + mInputConsumerEnabled);
-        pw.print(innerPrefix); pw.println("mTargetActivityRecord=" + mTargetActivityRecord);
-        pw.print(innerPrefix); pw.println("isTargetOverWallpaper=" + isTargetOverWallpaper());
-        pw.print(innerPrefix); pw.println("mRequestDeferCancelUntilNextTransition="
-                + mRequestDeferCancelUntilNextTransition);
-        pw.print(innerPrefix); pw.println("mCancelOnNextTransitionStart="
-                + mCancelOnNextTransitionStart);
-        pw.print(innerPrefix); pw.println("mCancelDeferredWithScreenshot="
-                + mCancelDeferredWithScreenshot);
-        pw.print(innerPrefix); pw.println("mPendingCancelWithScreenshotReorderMode="
-                + mPendingCancelWithScreenshotReorderMode);
-    }
-}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index f8665c7..432089f 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -53,6 +53,7 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.function.Consumer;
 
 /**
  * Helper class to run app animations in a remote process.
@@ -348,6 +349,10 @@
             } finally {
                 mIsFinishing = false;
             }
+            // Reset input for all activities when the remote animation is finished.
+            final Consumer<ActivityRecord> updateActivities =
+                    activity -> activity.setDropInputForAnimation(false);
+            mDisplayContent.forAllActivities(updateActivities);
         }
         setRunningRemoteAnimation(false);
         ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation");
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 862f84d..866dcd5 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -804,12 +804,6 @@
 
         checkAppTransitionReady(surfacePlacer);
 
-        // Defer starting the recents animation until the wallpaper has drawn
-        final RecentsAnimationController recentsAnimationController =
-                mWmService.getRecentsAnimationController();
-        if (recentsAnimationController != null) {
-            recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);
-        }
         mWmService.mAtmService.mBackNavigationController
                 .checkAnimationReady(defaultDisplay.mWallpaperController);
 
@@ -1471,9 +1465,6 @@
         // Updates the extra information of the intent.
         if (fromHomeKey) {
             homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
-            if (mWindowManager.getRecentsAnimationController() != null) {
-                mWindowManager.getRecentsAnimationController().cancelAnimationForHomeStart();
-            }
         }
         homeIntent.putExtra(WindowManagerPolicy.EXTRA_START_REASON, reason);
 
@@ -2041,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");
@@ -2282,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/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7c875c1..2ea2aeb 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -38,7 +38,6 @@
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -72,7 +71,6 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 import android.view.View;
 import android.view.View.FocusDirection;
 import android.view.WindowInsets;
@@ -108,7 +106,6 @@
     @NonNull
     final WindowProcessController mProcess;
     private final String mStringName;
-    SurfaceSession mSurfaceSession;
     private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
     /** Set of visible alert/app-overlay windows connected to this session. */
     private final ArraySet<WindowState> mAlertWindows = new ArraySet<>();
@@ -719,12 +716,10 @@
             mPackageName = mProcess.mInfo.packageName;
             mRelayoutTag = "relayoutWindow: " + mPackageName;
         }
-        if (mSurfaceSession == null) {
+        if (mProcess.mWindowSession == null) {
             if (DEBUG) {
-                Slog.v(TAG_WM, "First window added to " + this + ", creating SurfaceSession");
+                Slog.v(TAG_WM, "First window added to " + mProcess);
             }
-            mSurfaceSession = new SurfaceSession();
-            ProtoLog.i(WM_SHOW_TRANSACTIONS, "  NEW SURFACE SESSION %s", mSurfaceSession);
             mService.mSessions.add(this);
             if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                 mService.dispatchNewAnimatorScaleLocked(this);
@@ -821,18 +816,11 @@
         }
 
         mService.mSessions.remove(this);
-        if (mSurfaceSession == null) {
+        if (mProcess.mWindowSession == null) {
             return;
         }
 
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, "  KILL SURFACE SESSION %s", mSurfaceSession);
-        try {
-            mSurfaceSession.kill();
-        } catch (Exception e) {
-            Slog.w(TAG_WM, "Exception thrown when killing surface session " + mSurfaceSession
-                    + " in session " + this + ": " + e.toString());
-        }
-        mSurfaceSession = null;
+        mProcess.mWindowSession = null;
         mAddedWindows.clear();
         mAlertWindows.clear();
         setHasOverlayUi(false);
@@ -857,7 +845,6 @@
                 pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
                 pw.print(" mAlertWindows="); pw.print(mAlertWindows);
                 pw.print(" mClientDead="); pw.print(mClientDead);
-                pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
         pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName);
         if (isSatellitePointingUiPackage()) {
             pw.print(prefix); pw.println("mIsSatellitePointingUiPackage=true");
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/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 9cfd396..57f9be0 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -32,8 +32,8 @@
 import android.view.SurfaceControl.Transaction;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.LogLevel;
 import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.common.LogLevel;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -585,7 +585,6 @@
             ANIMATION_TYPE_APP_TRANSITION,
             ANIMATION_TYPE_SCREEN_ROTATION,
             ANIMATION_TYPE_DIMMER,
-            ANIMATION_TYPE_RECENTS,
             ANIMATION_TYPE_WINDOW_ANIMATION,
             ANIMATION_TYPE_INSETS_CONTROL,
             ANIMATION_TYPE_TOKEN_TRANSFORM,
@@ -604,7 +603,6 @@
             case ANIMATION_TYPE_APP_TRANSITION: return "app_transition";
             case ANIMATION_TYPE_SCREEN_ROTATION: return "screen_rotation";
             case ANIMATION_TYPE_DIMMER: return "dimmer";
-            case ANIMATION_TYPE_RECENTS: return "recents_animation";
             case ANIMATION_TYPE_WINDOW_ANIMATION: return "window_animation";
             case ANIMATION_TYPE_INSETS_CONTROL: return "insets_animation";
             case ANIMATION_TYPE_TOKEN_TRANSFORM: return "token_transform";
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 92813a8..8c93b4fe 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -61,7 +61,6 @@
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -95,7 +94,6 @@
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
 import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.TaskProto.AFFINITY;
 import static com.android.server.wm.TaskProto.BOUNDS;
 import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
@@ -168,7 +166,6 @@
 import android.view.RemoteAnimationAdapter;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
-import android.view.WindowManager.TransitionOldType;
 import android.window.ITaskOrganizer;
 import android.window.PictureInPictureSurfaceTransaction;
 import android.window.StartingWindowInfo;
@@ -499,6 +496,13 @@
      */
     boolean mIsTrimmableFromRecents;
 
+    /**
+     * Bounds offset should be applied when calculating compatible configuration for apps targeting
+     * SDK level 34 or before.
+     */
+    int mOffsetXForInsets;
+    int mOffsetYForInsets;
+
     private final AnimatingActivityRegistry mAnimatingActivityRegistry =
             new AnimatingActivityRegistry();
 
@@ -1278,8 +1282,8 @@
         EventLogTags.writeWmTaskMoved(mTaskId, getRootTaskId(), getDisplayId(), toTop ? 1 : 0,
                 position);
         final TaskDisplayArea taskDisplayArea = getDisplayArea();
-        if (taskDisplayArea != null && isLeafTask()) {
-            taskDisplayArea.onLeafTaskMoved(this, toTop, toBottom);
+        if (taskDisplayArea != null) {
+            taskDisplayArea.onTaskMoved(this, toTop, toBottom);
         }
         if (isPersistable) {
             mLastTimeMoved = System.currentTimeMillis();
@@ -2950,14 +2954,6 @@
         if (isOrganized()) {
             return false;
         }
-        // Don't animate while the task runs recents animation but only if we are in the mode
-        // where we cancel with deferred screenshot, which means that the controller has
-        // transformed the task.
-        final RecentsAnimationController controller = mWmService.getRecentsAnimationController();
-        if (controller != null && controller.isAnimatingTask(this)
-                && controller.shouldDeferCancelUntilNextTransition()) {
-            return false;
-        }
         return true;
     }
 
@@ -2969,8 +2965,7 @@
 
     /** Checking if self or its child tasks are animated by recents animation. */
     boolean isAnimatingByRecents() {
-        return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS)
-                || mTransitionController.isTransientHide(this);
+        return mTransitionController.isTransientHide(this);
     }
 
     WindowState getTopVisibleAppMainWindow() {
@@ -3275,30 +3270,6 @@
     }
 
     @Override
-    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
-            @TransitionOldType int transit, boolean isVoiceInteraction,
-            @Nullable ArrayList<WindowContainer> sources) {
-        final RecentsAnimationController control = mWmService.getRecentsAnimationController();
-        if (control != null) {
-            // We let the transition to be controlled by RecentsAnimation, and callback task's
-            // RemoteAnimationTarget for remote runner to animate.
-            if (enter && !isActivityTypeHomeOrRecents()) {
-                ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                        "applyAnimationUnchecked, control: %s, task: %s, transit: %s",
-                        control, asTask(), AppTransition.appTransitionOldToString(transit));
-                final int size = sources != null ? sources.size() : 0;
-                control.addTaskToTargets(this, (type, anim) -> {
-                    for (int i = 0; i < size; ++i) {
-                        sources.get(i).onAnimationFinished(type, anim);
-                    }
-                });
-            }
-        } else {
-            super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
-        }
-    }
-
-    @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         super.dump(pw, prefix, dumpAll);
         mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix);
@@ -3536,7 +3507,7 @@
      * {@link android.window.TaskFragmentOrganizer}
      */
     TaskFragmentParentInfo getTaskFragmentParentInfo() {
-        return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(),
+        return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), mTaskId,
                 shouldBeVisible(null /* starting */), hasNonFinishingDirectActivity(),
                 getDecorSurface());
     }
@@ -5874,6 +5845,10 @@
         super.dumpInner(prefix, pw, dumpAll, dumpPackage);
         if (mCreatedByOrganizer) {
             pw.println(prefix + "  mCreatedByOrganizer=true");
+            if (mOffsetXForInsets != 0 || mOffsetYForInsets != 0) {
+                pw.println(prefix + "  mOffsetXForInsets=" + mOffsetXForInsets
+                        + " mOffsetYForInsets=" + mOffsetYForInsets);
+            }
         }
         if (mLastNonFullscreenBounds != null) {
             pw.print(prefix); pw.print("  mLastNonFullscreenBounds=");
@@ -6130,9 +6105,7 @@
 
         if (canBeLaunchedOnDisplay(newParent.getDisplayId())) {
             reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);
-            if (isLeafTask()) {
-                newParent.onLeafTaskMoved(this, onTop, !onTop);
-            }
+            newParent.onTaskMoved(this, onTop, !onTop);
         } else {
             Slog.w(TAG, "Task=" + this + " can't reparent to " + newParent);
         }
@@ -6221,26 +6194,6 @@
         ActivityOptions.abort(options);
     }
 
-    boolean shouldSleepActivities() {
-        final DisplayContent display = mDisplayContent;
-        final boolean isKeyguardGoingAway = (mDisplayContent != null)
-                ? mDisplayContent.isKeyguardGoingAway()
-                : mRootWindowContainer.getDefaultDisplay().isKeyguardGoingAway();
-
-        // Do not sleep activities in this root task if we're marked as focused and the keyguard
-        // is in the process of going away.
-        if (isKeyguardGoingAway && isFocusedRootTaskOnDisplay()
-                // Avoid resuming activities on secondary displays since we don't want bubble
-                // activities to be resumed while bubble is still collapsed.
-                // TODO(b/113840485): Having keyguard going away state for secondary displays.
-                && display != null
-                && display.isDefaultDisplay) {
-            return false;
-        }
-
-        return display != null ? display.isSleeping() : mAtmService.isSleepingLocked();
-    }
-
     private Rect getRawBounds() {
         return super.getBounds();
     }
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index d9e88e1..638e92f 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -37,6 +37,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.ColorInt;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.WindowConfiguration;
@@ -142,13 +143,6 @@
      * current focused root task.
      */
     Task mLastFocusedRootTask;
-    /**
-     * All of the root tasks on this display. Order matters, topmost root task is in front of all
-     * other root tasks, bottommost behind. Accessed directly by ActivityManager package classes.
-     * Any calls changing the list should also call {@link #onRootTaskOrderChanged(Task)}.
-     */
-    private ArrayList<OnRootTaskOrderChangedListener> mRootTaskOrderChangedCallbacks =
-            new ArrayList<>();
 
     /**
      * The task display area is removed from the system and we are just waiting for all activities
@@ -331,7 +325,6 @@
         mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("addChildTask");
 
         mAtmService.updateSleepIfNeededLocked();
-        onRootTaskOrderChanged(task);
     }
 
     @Override
@@ -423,10 +416,6 @@
 
         // Update the top resumed activity because the preferred top focusable task may be changed.
         mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded("positionChildTaskAt");
-
-        if (mChildren.indexOf(child) != oldPosition) {
-            onRootTaskOrderChanged(child);
-        }
     }
 
     void onLeafTaskRemoved(int taskId) {
@@ -435,7 +424,19 @@
         }
     }
 
-    void onLeafTaskMoved(Task t, boolean toTop, boolean toBottom) {
+    void onTaskMoved(@NonNull Task t, boolean toTop, boolean toBottom) {
+        if (toBottom && !t.isLeafTask()) {
+            // Return early when a non-leaf task moved to bottom, to prevent sending duplicated
+            // leaf task movement callback if the leaf task is moved along with its parent tasks.
+            // Unless, we also track the task id, like `mLastLeafTaskToFrontId`.
+            return;
+        }
+
+        final Task topLeafTask = t.getTopLeafTask();
+        onLeafTaskMoved(topLeafTask, toTop, toBottom);
+    }
+
+    void onLeafTaskMoved(@NonNull Task t, boolean toTop, boolean toBottom) {
         if (toBottom) {
             mAtmService.getTaskChangeNotificationController().notifyTaskMovedToBack(
                     t.getTaskInfo());
@@ -831,7 +832,6 @@
             mLaunchAdjacentFlagRootTask = null;
         }
         mDisplayContent.releaseSelfIfNeeded();
-        onRootTaskOrderChanged(rootTask);
     }
 
     /**
@@ -1730,35 +1730,6 @@
         return mRemoved;
     }
 
-    /**
-     * Adds a listener to be notified whenever the root task order in the display changes. Currently
-     * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
-     * current animation when the system state changes.
-     */
-    void registerRootTaskOrderChangedListener(OnRootTaskOrderChangedListener listener) {
-        if (!mRootTaskOrderChangedCallbacks.contains(listener)) {
-            mRootTaskOrderChangedCallbacks.add(listener);
-        }
-    }
-
-    /**
-     * Removes a previously registered root task order change listener.
-     */
-    void unregisterRootTaskOrderChangedListener(OnRootTaskOrderChangedListener listener) {
-        mRootTaskOrderChangedCallbacks.remove(listener);
-    }
-
-    /**
-     * Notifies of a root task order change
-     *
-     * @param rootTask The root task which triggered the order change
-     */
-    void onRootTaskOrderChanged(Task rootTask) {
-        for (int i = mRootTaskOrderChangedCallbacks.size() - 1; i >= 0; i--) {
-            mRootTaskOrderChangedCallbacks.get(i).onRootTaskOrderChanged(rootTask);
-        }
-    }
-
     @Override
     boolean canCreateRemoteAnimationTarget() {
         // In the legacy transition system, promoting animation target from TaskFragment to
@@ -1773,13 +1744,6 @@
         return mDisplayContent.isHomeSupported() && mCanHostHomeTask;
     }
 
-    /**
-     * Callback for when the order of the root tasks in the display changes.
-     */
-    interface OnRootTaskOrderChangedListener {
-        void onRootTaskOrderChanged(Task rootTask);
-    }
-
     void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
         mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2fbabc5..f58b322 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2145,8 +2145,22 @@
     }
 
     boolean shouldSleepActivities() {
-        final Task task = getRootTask();
-        return task != null && task.shouldSleepActivities();
+        final DisplayContent dc = mDisplayContent;
+        if (dc == null) {
+            return mAtmService.isSleepingLocked();
+        }
+        if (!dc.isSleeping()) {
+            return false;
+        }
+        // In case the unlocking order is keyguard-going-away -> screen-turning-on (display is
+        // sleeping by screen-off-token which may be notified to release from power manager's
+        // thread), keep the activities resume-able to avoid extra activity lifecycle when
+        // performing keyguard-going-away. This only applies to default display because currently
+        // the per-display keyguard-going-away state is assigned from a global signal.
+        if (!dc.isDefaultDisplay || !dc.isKeyguardGoingAway()) {
+            return true;
+        }
+        return !shouldBeVisible(null /* starting */);
     }
 
     @Override
@@ -2224,7 +2238,7 @@
 
     static class ConfigOverrideHint {
         @Nullable DisplayInfo mTmpOverrideDisplayInfo;
-        @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
+        @Nullable AppCompatDisplayInsets mTmpCompatInsets;
         @Nullable Rect mParentAppBoundsOverride;
         int mTmpOverrideConfigOrientation;
         boolean mUseOverrideInsetsForConfig;
@@ -2294,7 +2308,7 @@
     void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
             @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
         DisplayInfo overrideDisplayInfo = null;
-        ActivityRecord.CompatDisplayInsets compatInsets = null;
+        AppCompatDisplayInsets compatInsets = null;
         boolean useOverrideInsetsForConfig = false;
         if (overrideHint != null) {
             overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index e4a3176..5aa34d2 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -46,6 +46,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.RemoteAnimationDefinition;
 import android.view.WindowManager;
 import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskFragmentOrganizerController;
@@ -156,6 +157,13 @@
         private final boolean mIsSystemOrganizer;
 
         /**
+         * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
+         * organized by this organizer.
+         */
+        @Nullable
+        private RemoteAnimationDefinition mRemoteAnimationDefinition;
+
+        /**
          * Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
          * {@link Transition#getSyncId()} that has been deferred. {@link TransitionController} will
          * wait until the organizer finished handling the {@link TaskFragmentTransaction}.
@@ -592,6 +600,50 @@
     }
 
     @Override
+    public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
+            @NonNull RemoteAnimationDefinition definition) {
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        synchronized (mGlobalLock) {
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+                    "Register remote animations for organizer=%s uid=%d pid=%d",
+                    organizer.asBinder(), uid, pid);
+            final TaskFragmentOrganizerState organizerState =
+                    mTaskFragmentOrganizerState.get(organizer.asBinder());
+            if (organizerState == null) {
+                throw new IllegalStateException("The organizer hasn't been registered.");
+            }
+            if (organizerState.mRemoteAnimationDefinition != null) {
+                throw new IllegalStateException(
+                        "The organizer has already registered remote animations="
+                                + organizerState.mRemoteAnimationDefinition);
+            }
+
+            definition.setCallingPidUid(pid, uid);
+            organizerState.mRemoteAnimationDefinition = definition;
+        }
+    }
+
+    @Override
+    public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
+        final int pid = Binder.getCallingPid();
+        final long uid = Binder.getCallingUid();
+        synchronized (mGlobalLock) {
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+                    "Unregister remote animations for organizer=%s uid=%d pid=%d",
+                    organizer.asBinder(), uid, pid);
+            final TaskFragmentOrganizerState organizerState =
+                    mTaskFragmentOrganizerState.get(organizer.asBinder());
+            if (organizerState == null) {
+                Slog.e(TAG, "The organizer hasn't been registered.");
+                return;
+            }
+
+            organizerState.mRemoteAnimationDefinition = null;
+        }
+    }
+
+    @Override
     public void setSavedState(@NonNull ITaskFragmentOrganizer organizer, @Nullable Bundle state) {
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
@@ -649,6 +701,25 @@
         }
     }
 
+    /**
+     * Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
+     * {@code null} if it doesn't.
+     */
+    @Nullable
+    public RemoteAnimationDefinition getRemoteAnimationDefinition(
+            @NonNull ITaskFragmentOrganizer organizer) {
+        synchronized (mGlobalLock) {
+            final TaskFragmentOrganizerState organizerState =
+                    mTaskFragmentOrganizerState.get(organizer.asBinder());
+            if (organizerState == null) {
+                Slog.e(TAG, "TaskFragmentOrganizer has been unregistered or died when trying"
+                        + " to play animation on its organized windows.");
+                return null;
+            }
+            return organizerState.mRemoteAnimationDefinition;
+        }
+    }
+
     int getTaskFragmentOrganizerUid(@NonNull ITaskFragmentOrganizer organizer) {
         final TaskFragmentOrganizerState state = validateAndGetState(organizer);
         return state.mOrganizerUid;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e76e94d..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);
@@ -1453,7 +1460,7 @@
             // Clean up input monitors (for recents)
             final DisplayContent dc =
                     mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
-            dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
+            dc.getInputMonitor().setActiveRecents(null /* task */, null /* layer */);
             dc.getInputMonitor().updateInputWindowsLw(false /* force */);
         }
         if (mTransientLaunches != null) {
@@ -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() {
@@ -2249,7 +2260,7 @@
         // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
         final InputConsumerImpl recentsAnimationInputConsumer =
                 dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
-        ActivityRecord recentsActivity = null;
+        Task recentsTask = null;
         if (recentsAnimationInputConsumer != null) {
             // Find the top-most going-away task and the recents activity. The top-most
             // is used as layer reference while the recents is used for registering the consumer
@@ -2264,20 +2275,20 @@
                 final int activityType = taskInfo.topActivityType;
                 final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
                         || activityType == ACTIVITY_TYPE_RECENTS;
-                if (isRecents && recentsActivity == null) {
-                    recentsActivity = task.getTopVisibleActivity();
+                if (isRecents && recentsTask == null) {
+                    recentsTask = task;
                 } else if (!isRecents && topNonRecentsTask == null) {
                     topNonRecentsTask = task;
                 }
             }
-            if (recentsActivity != null && topNonRecentsTask != null) {
+            if (recentsTask != null && topNonRecentsTask != null) {
                 recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
                         topNonRecentsTask.getBounds());
-                dc.getInputMonitor().setActiveRecents(recentsActivity, topNonRecentsTask);
+                dc.getInputMonitor().setActiveRecents(recentsTask, topNonRecentsTask);
             }
         }
 
-        if (recentsActivity == null) {
+        if (recentsTask == null) {
             // No recents activity on `dc`, its probably on a different display.
             return;
         }
@@ -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 1d2b693..1d2a605 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -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/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index f2615f7..f1941af 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -95,7 +95,7 @@
         }
         final boolean wasStarted = mTransparentPolicyState.isRunning();
         mTransparentPolicyState.reset();
-        // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the
+        // In case mActivityRecord.hasAppCompatDisplayInsetsWithoutOverride() we don't apply the
         // opaque activity constraints because we're expecting the activity is already letterboxed.
         final ActivityRecord firstOpaqueActivity = mActivityRecord.getTask().getActivity(
                 FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
@@ -159,12 +159,12 @@
         return mTransparentPolicyState.mInheritedOrientation;
     }
 
-    ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
-        return mTransparentPolicyState.mInheritedCompatDisplayInsets;
+    AppCompatDisplayInsets getInheritedAppCompatDisplayInsets() {
+        return mTransparentPolicyState.mInheritedAppCompatDisplayInsets;
     }
 
-    void clearInheritedCompatDisplayInsets() {
-        mTransparentPolicyState.clearInheritedCompatDisplayInsets();
+    void clearInheritedAppCompatDisplayInsets() {
+        mTransparentPolicyState.clearInheritedAppCompatDisplayInsets();
     }
 
     /**
@@ -201,8 +201,10 @@
             // never has letterbox.
             return true;
         }
+        final AppCompatSizeCompatModePolicy scmPolicy = mActivityRecord.mAppCompatController
+                .getAppCompatSizeCompatModePolicy();
         if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
-                || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) {
+                || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()) {
             return true;
         }
         return false;
@@ -239,9 +241,9 @@
         // The app compat state for the opaque activity if any
         private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
 
-        // The CompatDisplayInsets of the opaque activity beneath the translucent one.
+        // The AppCompatDisplayInsets of the opaque activity beneath the translucent one.
         @Nullable
-        private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;
+        private AppCompatDisplayInsets mInheritedAppCompatDisplayInsets;
 
         @Nullable
         private ActivityRecord mFirstOpaqueActivity;
@@ -303,7 +305,7 @@
             }
             mInheritedOrientation = opaqueActivity.getRequestedConfigurationOrientation();
             mInheritedAppCompatState = opaqueActivity.getAppCompatState();
-            mInheritedCompatDisplayInsets = opaqueActivity.getCompatDisplayInsets();
+            mInheritedAppCompatDisplayInsets = opaqueActivity.getAppCompatDisplayInsets();
         }
 
         private void reset() {
@@ -315,7 +317,7 @@
             mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
             mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
             mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
-            mInheritedCompatDisplayInsets = null;
+            mInheritedAppCompatDisplayInsets = null;
             if (mFirstOpaqueActivity != null) {
                 mFirstOpaqueActivity.mAppCompatController.getTransparentPolicy()
                         .mDestroyListeners.remove(mActivityRecord.mAppCompatController
@@ -340,8 +342,8 @@
                     || !mActivityRecord.handlesOrientationChangeFromDescendant(orientation);
         }
 
-        private void clearInheritedCompatDisplayInsets() {
-            mInheritedCompatDisplayInsets = null;
+        private void clearInheritedAppCompatDisplayInsets() {
+            mInheritedAppCompatDisplayInsets = null;
         }
 
         /**
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index 9b868be..030e848 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -51,7 +51,7 @@
 
     void requireOverlaySurfaceControl() {
         if (mSurfaceControl == null) {
-            final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null)
+            final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder()
                 .setContainerLayer()
                 .setHidden(true)
                 .setCallsite("TrustedOverlayHost.requireOverlaySurfaceControl")
@@ -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/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 4536f24..06010bb 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -172,8 +172,8 @@
                 && animatingContainer.getAnimation() != null
                 && animatingContainer.getAnimation().getShowWallpaper();
         final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper;
-        if (isRecentsTransitionTarget(w) || isBackNavigationTarget(w)) {
-            if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w);
+        if (isBackNavigationTarget(w)) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Found back animation wallpaper target: " + w);
             mFindResults.setWallpaperTarget(w);
             return true;
         } else if (hasWallpaper
@@ -199,15 +199,6 @@
         return false;
     };
 
-    private boolean isRecentsTransitionTarget(WindowState w) {
-        if (w.mTransitionController.isShellTransitionsEnabled()) {
-            return false;
-        }
-        // The window is either the recents activity or is in the task animating by the recents.
-        final RecentsAnimationController controller = mService.getRecentsAnimationController();
-        return controller != null && controller.isWallpaperVisible(w);
-    }
-
     private boolean isBackNavigationTarget(WindowState w) {
         // The window is in animating by back navigation and set to show wallpaper.
         return mService.mAtmService.mBackNavigationController.isWallpaperVisible(w);
@@ -928,12 +919,6 @@
                 Slog.v(TAG, "*** WALLPAPER DRAW TIMEOUT");
             }
 
-            // If there was a pending recents animation, start the animation anyways (it's better
-            // to not see the wallpaper than for the animation to not start)
-            if (mService.getRecentsAnimationController() != null) {
-                mService.getRecentsAnimationController().startAnimation();
-            }
-
             // If there was a pending back navigation animation that would show wallpaper, start
             // the animation due to it was skipped in previous surface placement.
             mService.mAtmService.mBackNavigationController.startAnimation();
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 384d111..89ad564 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -161,15 +161,7 @@
                 mDisplayContent.mWallpaperController.getWallpaperTarget();
 
         if (visible && wallpaperTarget != null) {
-            final RecentsAnimationController recentsAnimationController =
-                    mWmService.getRecentsAnimationController();
-            if (recentsAnimationController != null
-                    && recentsAnimationController.isAnimatingTask(wallpaperTarget.getTask())) {
-                // If the Recents animation is running, and the wallpaper target is the animating
-                // task we want the wallpaper to be rotated in the same orientation as the
-                // RecentsAnimation's target (e.g the launcher)
-                recentsAnimationController.linkFixedRotationTransformIfNeeded(this);
-            } else if ((wallpaperTarget.mActivityRecord == null
+            if ((wallpaperTarget.mActivityRecord == null
                     // Ignore invisible activity because it may be moving to background.
                     || wallpaperTarget.mActivityRecord.isVisibleRequested())
                     && wallpaperTarget.mToken.hasFixedRotationTransform()) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 03342d3..13334a5 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -19,7 +19,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -218,8 +217,8 @@
     private void updateRunningExpensiveAnimationsLegacy() {
         final boolean runningExpensiveAnimations =
                 mService.mRoot.isAnimating(TRANSITION | CHILDREN /* flags */,
-                        ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_SCREEN_ROTATION
-                                | ANIMATION_TYPE_RECENTS /* typesToCheck */);
+                        ANIMATION_TYPE_APP_TRANSITION
+                                | ANIMATION_TYPE_SCREEN_ROTATION /* typesToCheck */);
         if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
             mService.mSnapshotController.setPause(true);
             mTransaction.setEarlyWakeupStart();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9ae881b..1eeb3ec 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -50,7 +51,6 @@
 import static com.android.server.wm.IdentifierProto.USER_ID;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -100,7 +100,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Builder;
 import android.view.SurfaceControlViewHost;
-import android.view.SurfaceSession;
 import android.view.WindowManager;
 import android.view.WindowManager.TransitionOldType;
 import android.view.animation.Animation;
@@ -706,7 +705,7 @@
         mLastSurfacePosition.set(0, 0);
         mLastDeltaRotation = Surface.ROTATION_0;
 
-        final Builder b = mWmService.makeSurfaceBuilder(null)
+        final Builder b = mWmService.makeSurfaceBuilder()
                 .setContainerLayer()
                 .setName(getName());
 
@@ -1241,8 +1240,7 @@
      */
     boolean inTransitionSelfOrParent() {
         if (!mTransitionController.isShellTransitionsEnabled()) {
-            return isAnimating(PARENTS | TRANSITION,
-                    ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+            return isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION);
         }
         return inTransition();
     }
@@ -1732,13 +1730,13 @@
      *         last time {@link #getOrientation(int) was called.
      */
     @Nullable
-    WindowContainer getLastOrientationSource() {
-        final WindowContainer source = mLastOrientationSource;
-        if (source != null && source != this) {
-            final WindowContainer nextSource = source.getLastOrientationSource();
-            if (nextSource != null) {
-                return nextSource;
-            }
+    final WindowContainer<?> getLastOrientationSource() {
+        if (mLastOrientationSource == null) {
+            return null;
+        }
+        WindowContainer<?> source = this;
+        while (source != source.mLastOrientationSource && source.mLastOrientationSource != null) {
+            source = source.mLastOrientationSource;
         }
         return source;
     }
@@ -2663,13 +2661,6 @@
         return true;
     }
 
-    SurfaceSession getSession() {
-        if (getParent() != null) {
-            return getParent().getSession();
-        }
-        return null;
-    }
-
     void assignLayer(Transaction t, int layer) {
         // Don't assign layers while a transition animation is playing
         // TODO(b/173528115): establish robust best-practices around z-order fighting.
@@ -2997,12 +2988,18 @@
         // Make sure display isn't a part of the transition already - needed for legacy transitions.
         if (mDisplayContent.inTransition()) return false;
 
-        if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) {
-            // Screenshots are turned off when PiP is undergoing changes.
-            return !inPinnedWindowingMode() && getParent() != null
-                    && !getParent().inPinnedWindowingMode();
-        }
-        return true;
+        // Screenshots are turned off when PiP is undergoing changes.
+        return ActivityTaskManagerService.isPip2ExperimentEnabled() || !isPipChange();
+    }
+
+    /** Returns true if WC is pinned and undergoing changes. */
+    private boolean isPipChange() {
+        final boolean isExitingPip = this.asTaskFragment() != null
+                && mTransitionController.getWindowingModeAtStart(this) == WINDOWING_MODE_PINNED
+                && !inPinnedWindowingMode();
+
+        return isExitingPip || inPinnedWindowingMode()
+                || (getParent() != null && getParent().inPinnedWindowingMode());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index 57fc4c7..80f3c44 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -20,7 +20,7 @@
 import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
 import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
 import static com.android.server.wm.WindowContainerThumbnailProto.WIDTH;
@@ -118,14 +118,7 @@
                         mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(),
                         mWindowContainer.getDisplayContent().getWindowCornerRadius()),
                 mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */,
-                ANIMATION_TYPE_RECENTS);
-    }
-
-    /**
-     * Start animation with existing adapter.
-     */
-    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
-        mSurfaceAnimator.startAnimation(t, anim, hidden, ANIMATION_TYPE_RECENTS);
+                ANIMATION_TYPE_APP_TRANSITION);
     }
 
     private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index 1931be4..47c42f4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -34,6 +34,10 @@
  */
 final class WindowManagerConstants {
 
+    /** The orientation of activity will be always "unspecified". */
+    private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST =
+            "ignore_activity_orientation_request";
+
     /**
      * The minimum duration between gesture exclusion logging for a given window in
      * milliseconds.
@@ -58,6 +62,9 @@
     /** @see AndroidDeviceConfig#KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE */
     boolean mSystemGestureExcludedByPreQStickyImmersive;
 
+    /** @see #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST */
+    boolean mIgnoreActivityOrientationRequest;
+
     private final WindowManagerGlobalLock mGlobalLock;
     private final Runnable mUpdateSystemGestureExclusionCallback;
     private final DeviceConfigInterface mDeviceConfig;
@@ -89,6 +96,7 @@
         updateSystemGestureExclusionLogDebounceMillis();
         updateSystemGestureExclusionLimitDp();
         updateSystemGestureExcludedByPreQStickyImmersive();
+        updateIgnoreActivityOrientationRequest();
     }
 
     private void onAndroidPropertiesChanged(DeviceConfig.Properties properties) {
@@ -127,6 +135,9 @@
                     case KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS:
                         updateSystemGestureExclusionLogDebounceMillis();
                         break;
+                    case KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST:
+                        updateIgnoreActivityOrientationRequest();
+                        break;
                     default:
                         break;
                 }
@@ -152,6 +163,12 @@
                 KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
     }
 
+    private void updateIgnoreActivityOrientationRequest() {
+        mIgnoreActivityOrientationRequest = mDeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST, false);
+    }
+
     void dump(PrintWriter pw) {
         pw.println("WINDOW MANAGER CONSTANTS (dumpsys window constants):");
 
@@ -161,6 +178,8 @@
         pw.print("="); pw.println(mSystemGestureExclusionLimitDp);
         pw.print("  "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE);
         pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
+        pw.print("  "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST);
+        pw.print("="); pw.println(mIgnoreActivityOrientationRequest);
         pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d8df645..fb57a1b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -129,7 +129,6 @@
 import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -246,7 +245,6 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
@@ -266,7 +264,6 @@
 import android.view.IInputFilter;
 import android.view.IOnKeyguardExitResult;
 import android.view.IPinnedTaskListener;
-import android.view.IRecentsAnimationRunner;
 import android.view.IRotationWatcher;
 import android.view.IScrollCaptureResponseListener;
 import android.view.ISystemGestureExclusionListener;
@@ -291,7 +288,6 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
-import android.view.SurfaceSession;
 import android.view.View;
 import android.view.View.FocusDirection;
 import android.view.ViewDebug;
@@ -389,7 +385,6 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
 import java.util.function.Supplier;
 
 /** {@hide} */
@@ -681,7 +676,6 @@
     private final SparseIntArray mOrientationMapping = new SparseIntArray();
 
     final AccessibilityController mAccessibilityController;
-    private RecentsAnimationController mRecentsAnimationController;
 
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
@@ -1115,7 +1109,7 @@
     static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
             new WindowManagerThreadPriorityBooster();
 
-    Function<SurfaceSession, SurfaceControl.Builder> mSurfaceControlFactory;
+    Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
     Supplier<SurfaceControl.Transaction> mTransactionFactory;
 
     private final SurfaceControl.Transaction mTransaction;
@@ -1159,17 +1153,12 @@
                 return;
             }
 
-            // While running a recents animation, this will get called early because we show the
-            // recents animation target activity immediately when the animation starts. Defer the
-            // mLaunchTaskBehind updates until recents animation finishes.
-            if (atoken.mLaunchTaskBehind && !isRecentsAnimationTarget(atoken)) {
+            if (atoken.mLaunchTaskBehind) {
                 mAtmService.mTaskSupervisor.scheduleLaunchTaskBehindComplete(atoken.token);
                 atoken.mLaunchTaskBehind = false;
             } else {
                 atoken.updateReportedVisibilityLocked();
-                // We should also defer sending the finished callback until the recents animation
-                // successfully finishes.
-                if (atoken.mEnteringAnimation && !isRecentsAnimationTarget(atoken)) {
+                if (atoken.mEnteringAnimation) {
                     atoken.mEnteringAnimation = false;
                     if (atoken.attachedToProcess()) {
                         try {
@@ -1211,7 +1200,7 @@
             final boolean showBootMsgs, WindowManagerPolicy policy, ActivityTaskManagerService atm,
             DisplayWindowSettingsProvider displayWindowSettingsProvider,
             Supplier<SurfaceControl.Transaction> transactionFactory,
-            Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
+            Supplier<SurfaceControl.Builder> surfaceControlFactory) {
         final WindowManagerService[] wms = new WindowManagerService[1];
         DisplayThread.getHandler().runWithScissors(() ->
                 wms[0] = new WindowManagerService(context, im, showBootMsgs, policy, atm,
@@ -1240,7 +1229,7 @@
             boolean showBootMsgs, WindowManagerPolicy policy, ActivityTaskManagerService atm,
             DisplayWindowSettingsProvider displayWindowSettingsProvider,
             Supplier<SurfaceControl.Transaction> transactionFactory,
-            Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
+            Supplier<SurfaceControl.Builder> surfaceControlFactory) {
         installLock(this, INDEX_WINDOW);
         mGlobalLock = atm.getGlobalLock();
         mAtmService = atm;
@@ -1535,7 +1524,7 @@
         final boolean isRoundedCornerOverlay = (attrs.privateFlags
                 & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
         int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
-                appOp);
+                appOp, displayId);
         if (res != ADD_OKAY) {
             return res;
         }
@@ -2733,8 +2722,7 @@
                         win.mTransitionController.mAnimatingExitWindows.add(win);
                         reason = "inTransition";
                     }
-                } else if (win.isAnimating(PARENTS | TRANSITION,
-                        ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
+                } else if (win.isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
                     // Already animating as part of a legacy app-transition.
                     reason = "inLegacyTransition";
                 }
@@ -3168,7 +3156,7 @@
     }
 
     // TODO(multi-display): remove when no default display use case.
-    // (i.e. KeyguardController / RecentsAnimation)
+    // (i.e. KeyguardController)
     public void executeAppTransition() {
         if (!checkCallingPermission(MANAGE_APP_TOKENS, "executeAppTransition()")) {
             throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3176,57 +3164,6 @@
         getDefaultDisplayContentLocked().executeAppTransition();
     }
 
-    void initializeRecentsAnimation(int targetActivityType,
-            IRecentsAnimationRunner recentsAnimationRunner,
-            RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId,
-            SparseBooleanArray recentTaskIds, ActivityRecord targetActivity) {
-        mRecentsAnimationController = new RecentsAnimationController(this, recentsAnimationRunner,
-                callbacks, displayId);
-        mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
-        mRecentsAnimationController.initialize(targetActivityType, recentTaskIds, targetActivity);
-    }
-
-    @VisibleForTesting
-    void setRecentsAnimationController(RecentsAnimationController controller) {
-        mRecentsAnimationController = controller;
-    }
-
-    RecentsAnimationController getRecentsAnimationController() {
-        return mRecentsAnimationController;
-    }
-
-    void cancelRecentsAnimation(
-            @RecentsAnimationController.ReorderMode int reorderMode, String reason) {
-        if (mRecentsAnimationController != null) {
-            // This call will call through to cleanupAnimation() below after the animation is
-            // canceled
-            mRecentsAnimationController.cancelAnimation(reorderMode, reason);
-        }
-    }
-
-
-    void cleanupRecentsAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
-        if (mRecentsAnimationController != null) {
-            final RecentsAnimationController controller = mRecentsAnimationController;
-            mRecentsAnimationController = null;
-            controller.cleanupAnimation(reorderMode);
-            // TODO(multi-display): currently only default display support recents animation.
-            final DisplayContent dc = getDefaultDisplayContentLocked();
-            if (dc.mAppTransition.isTransitionSet()) {
-                dc.mSkipAppTransitionAnimation = true;
-            }
-            dc.forAllWindowContainers((wc) -> {
-                if (wc.isAnimating(TRANSITION, ANIMATION_TYPE_APP_TRANSITION)) {
-                    wc.cancelAnimation();
-                }
-            });
-        }
-    }
-
-    boolean isRecentsAnimationTarget(ActivityRecord r) {
-        return mRecentsAnimationController != null && mRecentsAnimationController.isTargetApp(r);
-    }
-
     boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) {
         return displayContent.getPinnedTaskController().isValidPictureInPictureAspectRatio(
                 aspectRatio);
@@ -3258,11 +3195,6 @@
     }
 
     @Override
-    public void triggerAnimationFailsafe() {
-        mH.sendEmptyMessage(H.ANIMATION_FAILSAFE);
-    }
-
-    @Override
     public void onKeyguardShowingAndNotOccludedChanged() {
         mH.sendEmptyMessage(H.RECOMPUTE_FOCUS);
         dispatchKeyguardLockedState();
@@ -5652,7 +5584,6 @@
         public static final int UPDATE_ANIMATION_SCALE = 51;
         public static final int WINDOW_HIDE_TIMEOUT = 52;
         public static final int SET_HAS_OVERLAY_UI = 58;
-        public static final int ANIMATION_FAILSAFE = 60;
         public static final int RECOMPUTE_FOCUS = 61;
         public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62;
         public static final int WINDOW_STATE_BLAST_SYNC_TIMEOUT = 64;
@@ -5887,14 +5818,6 @@
                     mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1);
                     break;
                 }
-                case ANIMATION_FAILSAFE: {
-                    synchronized (mGlobalLock) {
-                        if (mRecentsAnimationController != null) {
-                            mRecentsAnimationController.scheduleFailsafe();
-                        }
-                    }
-                    break;
-                }
                 case RECOMPUTE_FOCUS: {
                     synchronized (mGlobalLock) {
                         updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
@@ -6100,7 +6023,7 @@
                     displayContent.setForcedSize(displayContent.mInitialDisplayWidth,
                             displayContent.mInitialDisplayHeight,
                             displayContent.mInitialPhysicalXDpi,
-                            displayContent.mInitialPhysicalXDpi);
+                            displayContent.mInitialPhysicalYDpi);
                 }
             }
         } finally {
@@ -7036,10 +6959,6 @@
                     pw.print(" window="); pw.print(mWindowAnimationScaleSetting);
                     pw.print(" transition="); pw.print(mTransitionAnimationScaleSetting);
                     pw.print(" animator="); pw.println(mAnimatorDurationScaleSetting);
-            if (mRecentsAnimationController != null) {
-                pw.print("  mRecentsAnimationController="); pw.println(mRecentsAnimationController);
-                mRecentsAnimationController.dump(pw, "    ");
-            }
         }
     }
 
@@ -7999,7 +7918,7 @@
             }
             boolean allWindowsDrawn = false;
             synchronized (mGlobalLock) {
-                if (displayId == DEFAULT_DISPLAY
+                if (displayId == INVALID_DISPLAY
                         && mRoot.getDefaultDisplay().mDisplayUpdater.waitForTransition(message)) {
                     // Use the ready-to-play of transition as the signal.
                     return;
@@ -8600,7 +8519,7 @@
                     return null;
                 }
                 // TODO(b/210039666): Use a method like add/removeDisplayOverlay if available.
-                return makeSurfaceBuilder(dc.getSession())
+                return makeSurfaceBuilder()
                         .setContainerLayer()
                         .setName("IME Handwriting Surface")
                         .setCallsite("getHandwritingSurfaceForDisplay")
@@ -8908,8 +8827,8 @@
         }
     }
 
-    SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
-        return mSurfaceControlFactory.apply(s);
+    SurfaceControl.Builder makeSurfaceBuilder() {
+        return mSurfaceControlFactory.get();
     }
 
     /**
@@ -9024,6 +8943,22 @@
             // display it's on to the top since that window won't be able to get focus anyway.
             return;
         }
+
+        final ActivityRecord touchedApp = t.getActivityRecord();
+        if (touchedApp != null && touchedApp.getTask() != null) {
+            final ActivityRecord top = touchedApp.getTask().topRunningActivity();
+            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
+                // pointer-down-outside-focus event is delayed (after new activity started on top).
+                // In that case, drop the event to prevent changing focus to a background activity.
+                Slog.w(TAG, "onPointerDownOutsideFocusLocked, drop event because " + touchedApp
+                        + " is occluded and should not be focused.");
+                return;
+            }
+        }
+
         clearPointerDownOutsideFocusRunnable();
 
         if (shouldDelayTouchOutside(t)) {
@@ -9040,9 +8975,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;
@@ -9075,17 +9009,6 @@
             }
             clearPointerDownOutsideFocusRunnable();
 
-            if (mRecentsAnimationController != null
-                    && mRecentsAnimationController.getTargetAppMainWindow() == t) {
-                // If there is an active recents animation and touched window is the target,
-                // then ignore the touch. The target already handles touches using its own
-                // input monitor and we don't want to trigger any lifecycle changes from
-                // focusing another window.
-                // TODO(b/186770026): We should remove this once we support multiple resumed
-                //  activities while in overview
-                return;
-            }
-
             final WindowState w = t.getWindowState();
             if (w != null) {
                 final Task task = w.getTask();
@@ -10198,6 +10121,23 @@
         }
     }
 
+    /**
+     * Returns whether the given UID is the owner of a virtual device, which the given display
+     * belongs to.
+     */
+    @Override
+    public boolean isCallerVirtualDeviceOwner(int displayId, int callingUid) {
+        if (!android.companion.virtualdevice.flags.Flags.statusBarAndInsets()) {
+            return false;
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mAtmService.mTaskSupervisor.isDeviceOwnerUid(displayId, callingUid);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     @RequiresPermission(ACCESS_SURFACE_FLINGER)
     @Override
     public boolean replaceContentOnDisplay(int displayId, SurfaceControl sc) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9d40b16..476443a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -18,9 +18,12 @@
 
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.ActivityManager.isStartResultSuccessful;
+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;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
@@ -220,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);
@@ -239,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;
                 }
 
@@ -259,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
@@ -273,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);
                                 });
                     }
@@ -310,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 =
@@ -331,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);
@@ -348,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.
@@ -364,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();
                     }
@@ -472,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 {
@@ -490,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) {
@@ -497,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 {
@@ -534,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;
             }
 
@@ -545,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
@@ -556,7 +576,7 @@
                                     + " because there is an ongoing sync for"
                                     + " applySyncTransaction().");
                 }
-                applyTransaction(wct, -1 /* syncId */, transition, caller);
+                applyTransaction(wct, -1 /* syncId */, chain, caller);
                 return;
             }
 
@@ -567,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;
                 }
@@ -583,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
@@ -601,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();
@@ -621,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;
                 }
@@ -642,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();
@@ -652,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();
@@ -669,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
@@ -725,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
@@ -786,7 +799,7 @@
             }
         } finally {
             if (deferTransitionReady) {
-                transition.continueTransitionReady();
+                chain.mTransition.continueTransitionReady();
             }
             mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
             if (deferResume) {
@@ -818,6 +831,31 @@
                 final Configuration c =
                         new Configuration(container.getRequestedOverrideConfiguration());
                 c.setTo(change.getConfiguration(), configMask, windowMask);
+                if (container.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+                        && (change.getConfigSetMask() & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
+                    // Special handling for split screen window got offset. The insets calculation
+                    // for configuration should be stable regardless of the offset. Set offset to
+                    // the task level to be applied when calculate compat override for apps
+                    // targeting SDK level 34 or before.
+                    final Task task = container.asTask();
+                    if (task != null) {
+                        if (c.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED
+                                && c.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED) {
+                            final Rect oldBounds = container.getRequestedOverrideBounds();
+                            final Rect newBounds =
+                                    change.getConfiguration().windowConfiguration.getBounds();
+                            if (oldBounds.width() == newBounds.width()
+                                    && oldBounds.height() == newBounds.height()) {
+                                task.mOffsetXForInsets = oldBounds.left - newBounds.left;
+                                task.mOffsetYForInsets = oldBounds.top - newBounds.top;
+                            } else {
+                                task.mOffsetXForInsets = task.mOffsetYForInsets = 0;
+                            }
+                        } else {
+                            task.mOffsetXForInsets = task.mOffsetYForInsets = 0;
+                        }
+                    }
+                }
                 container.onRequestedOverrideConfigurationChanged(c);
             }
             effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
@@ -1051,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: {
@@ -1123,7 +1161,7 @@
                 break;
             }
             case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: {
-                effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId,
+                effects |= reparentChildrenTasksHierarchyOp(hop, chain.mTransition, syncId,
                         isInLockTaskMode);
                 break;
             }
@@ -1176,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 =
@@ -1191,7 +1229,7 @@
                                 Slog.e(TAG, "Can't resolve parent window from token");
                                 break;
                             }
-                            transition.collect(parentWc);
+                            chain.mTransition.collect(parentWc);
                         }
                     }
                 }
@@ -1205,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: {
@@ -1301,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
@@ -1320,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);
@@ -1416,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;
@@ -1439,7 +1477,7 @@
                     break;
                 }
                 createTaskFragment(taskFragmentCreationParams, errorCallbackToken, caller,
-                        transition);
+                        chain.mTransition);
                 break;
             }
             case OP_TYPE_DELETE_TASK_FRAGMENT: {
@@ -1456,7 +1494,7 @@
                         break;
                     }
                 }
-                effects |= deleteTaskFragment(taskFragment, transition);
+                effects |= deleteTaskFragment(taskFragment, chain.mTransition);
                 break;
             }
             case OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: {
@@ -1505,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;
@@ -1668,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) {
@@ -1721,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/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 2bae0a8..b6b36c7 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -89,6 +89,7 @@
 import com.android.server.Watchdog;
 import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
 import com.android.server.wm.ActivityTaskManagerService.HotPath;
+import com.android.server.wm.BackgroundLaunchProcessController.BalCheckConfiguration;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -284,14 +285,11 @@
     static final int ANIMATING_REASON_REMOTE_ANIMATION = 1;
     /** It is set for wakefulness transition. */
     static final int ANIMATING_REASON_WAKEFULNESS_CHANGE = 1 << 1;
-    /** Whether the legacy {@link RecentsAnimation} is running. */
-    static final int ANIMATING_REASON_LEGACY_RECENT_ANIMATION = 1 << 2;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
             ANIMATING_REASON_REMOTE_ANIMATION,
             ANIMATING_REASON_WAKEFULNESS_CHANGE,
-            ANIMATING_REASON_LEGACY_RECENT_ANIMATION,
     })
     @interface AnimatingReason {}
 
@@ -698,20 +696,13 @@
     public boolean areBackgroundFgsStartsAllowed() {
         return areBackgroundActivityStartsAllowed(
                 mAtm.getBalAppSwitchesState(),
-                true /* isCheckingForFgsStart */).allows();
+                BackgroundLaunchProcessController.CHECK_FOR_FGS_START).allows();
     }
 
     BackgroundActivityStartController.BalVerdict areBackgroundActivityStartsAllowed(
-            int appSwitchState) {
-        return areBackgroundActivityStartsAllowed(
-                appSwitchState,
-                false /* isCheckingForFgsStart */);
-    }
-
-    private BackgroundActivityStartController.BalVerdict areBackgroundActivityStartsAllowed(
-            int appSwitchState, boolean isCheckingForFgsStart) {
+            int appSwitchState, BalCheckConfiguration checkConfiguration) {
         return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid,
-                mInfo.packageName, appSwitchState, isCheckingForFgsStart,
+                mInfo.packageName, appSwitchState, checkConfiguration,
                 hasActivityInVisibleTask(), mInstrumentingWithBackgroundActivityStartPrivileges,
                 mAtm.getLastStopAppSwitchesTime(),
                 mLastActivityLaunchTime, mLastActivityFinishTime);
@@ -1689,7 +1680,8 @@
                 resolvedConfig,
                 false /* optsOutEdgeToEdge */,
                 false /* hasFixedRotationTransform */,
-                false /* hasCompatDisplayInsets */);
+                false /* hasCompatDisplayInsets */,
+                null /* task */);
     }
 
     void dispatchConfiguration(@NonNull Configuration config) {
@@ -2016,14 +2008,6 @@
         return mStoppedState == STOPPED_STATE_FIRST_LAUNCH;
     }
 
-    void setRunningRecentsAnimation(boolean running) {
-        if (running) {
-            addAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
-        } else {
-            removeAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
-        }
-    }
-
     void setRunningRemoteAnimation(boolean running) {
         if (running) {
             addAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
@@ -2118,9 +2102,6 @@
             if ((animatingReasons & ANIMATING_REASON_WAKEFULNESS_CHANGE) != 0) {
                 pw.print("wakefulness|");
             }
-            if ((animatingReasons & ANIMATING_REASON_LEGACY_RECENT_ANIMATION) != 0) {
-                pw.print("legacy-recents");
-            }
             pw.println();
         }
         if (mUseFifoUiScheduling) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4568f2e..256d0c6 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -128,7 +128,6 @@
 import static com.android.server.wm.MoveAnimationSpecProto.TO;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
@@ -234,7 +233,6 @@
 import android.view.Surface;
 import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewTreeObserver;
@@ -581,7 +579,7 @@
      * is guaranteed to be cleared.
      */
     static final int EXIT_ANIMATING_TYPES = ANIMATION_TYPE_APP_TRANSITION
-            | ANIMATION_TYPE_WINDOW_ANIMATION | ANIMATION_TYPE_RECENTS;
+            | ANIMATION_TYPE_WINDOW_ANIMATION;
 
     /** Currently running an exit animation? */
     boolean mAnimatingExit;
@@ -1705,18 +1703,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.
@@ -1971,13 +1957,9 @@
      * it must be drawn before allDrawn can become true.
      */
     boolean isInteresting() {
-        final RecentsAnimationController recentsAnimationController =
-                mWmService.getRecentsAnimationController();
         return mActivityRecord != null
                 && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
-                && mViewVisibility == View.VISIBLE
-                && (recentsAnimationController == null
-                         || recentsAnimationController.isInterestingForAllDrawn(this));
+                && mViewVisibility == View.VISIBLE;
     }
 
     /**
@@ -2575,10 +2557,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;
         }
 
@@ -2604,7 +2585,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;
         }
@@ -2790,11 +2771,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);
                 }
             }
         }
@@ -3002,7 +2981,8 @@
                 resolvedConfig,
                 (mAttrs.privateFlags & PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE) != 0,
                 false /* hasFixedRotationTransform */,
-                false /* hasCompatDisplayInsets */);
+                false /* hasCompatDisplayInsets */,
+                null /* task */);
     }
 
     /**
@@ -3938,14 +3918,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) {
@@ -4081,7 +4053,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);
@@ -4139,8 +4116,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());
@@ -4670,17 +4648,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);
     }
 
@@ -5194,15 +5161,6 @@
     }
 
     @Override
-    SurfaceSession getSession() {
-        if (mSession.mSurfaceSession != null) {
-            return mSession.mSurfaceSession;
-        } else {
-            return getParent().getSession();
-        }
-    }
-
-    @Override
     boolean needsZBoost() {
         final InsetsControlTarget target = getDisplayContent().getImeTarget(IME_TARGET_LAYERING);
         if (mIsImWindow && target != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index b40cf56..82fa9d4 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -321,8 +321,7 @@
         }
 
         if (DEBUG_VISIBILITY) {
-            Slog.v(TAG, "Creating surface in session "
-                    + mSession.mSurfaceSession + " window " + this
+            Slog.v(TAG, "Creating surface " + this
                     + " format=" + attrs.format + " flags=" + flags);
         }
 
@@ -358,9 +357,8 @@
             w.mInputWindowHandle.forceChange();
 
             ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
-                        "  CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s",
-                    mSurfaceControl, mSession.mSurfaceSession, mSession.mPid, attrs.format,
-                        flags, this);
+                    "  CREATE SURFACE %s: pid=%d format=%d flags=0x%x / %s",
+                    mSurfaceControl, mSession.mPid, attrs.format, flags, this);
         } catch (OutOfResourcesException e) {
             Slog.w(TAG, "OutOfResourcesException creating surface");
             mService.mRoot.reclaimSomeSurfaceMemory(this, "create", true);
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
index 6984f0d..dc048ef 100644
--- a/services/core/java/com/android/server/wm/WindowTracingDataSource.java
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -33,8 +33,8 @@
 import android.util.proto.ProtoInputStream;
 
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
 
 public final class WindowTracingDataSource extends DataSource<WindowTracingDataSource.Instance,
         WindowTracingDataSource.TlsState, Void> {
@@ -76,15 +76,11 @@
     private static final String TAG = "WindowTracingDataSource";
 
     @NonNull
-    private final Consumer<Config> mOnStartCallback;
-    @NonNull
-    private final Consumer<Config> mOnStopCallback;
+    private final WeakReference<WindowTracingPerfetto> mWindowTracing;
 
-    public WindowTracingDataSource(@NonNull Consumer<Config> onStart,
-            @NonNull Consumer<Config> onStop) {
+    public WindowTracingDataSource(WindowTracingPerfetto windowTracing) {
         super(DATA_SOURCE_NAME);
-        mOnStartCallback = onStart;
-        mOnStopCallback = onStop;
+        mWindowTracing = new WeakReference<>(windowTracing);
 
         Producer.init(InitArguments.DEFAULTS);
         DataSourceParams params =
@@ -93,6 +89,7 @@
                                 PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
                         .build();
         register(params);
+        Log.i(TAG, "Registered with perfetto service");
     }
 
     @Override
@@ -102,12 +99,18 @@
         return new Instance(this, instanceIndex, config != null ? config : CONFIG_DEFAULT) {
             @Override
             protected void onStart(StartCallbackArguments args) {
-                mOnStartCallback.accept(mConfig);
+                WindowTracingPerfetto windowTracing = mWindowTracing.get();
+                if (windowTracing != null) {
+                    windowTracing.onStart(mConfig);
+                }
             }
 
             @Override
             protected void onStop(StopCallbackArguments args) {
-                mOnStopCallback.accept(mConfig);
+                WindowTracingPerfetto windowTracing = mWindowTracing.get();
+                if (windowTracing != null) {
+                    windowTracing.onStop(mConfig);
+                }
             }
         };
     }
diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
index cf948ca..22d6c86 100644
--- a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
+++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
@@ -35,8 +35,7 @@
 
     private final AtomicInteger mCountSessionsOnFrame = new AtomicInteger();
     private final AtomicInteger mCountSessionsOnTransaction = new AtomicInteger();
-    private final WindowTracingDataSource mDataSource = new WindowTracingDataSource(
-            this::onStart, this::onStop);
+    private final WindowTracingDataSource mDataSource = new WindowTracingDataSource(this);
 
     WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) {
         this(service, choreographer, service.mGlobalLock);
@@ -156,7 +155,7 @@
         return mCountSessionsOnTransaction.get() > 0;
     }
 
-    private void onStart(WindowTracingDataSource.Config config) {
+    void onStart(WindowTracingDataSource.Config config) {
         if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
             mCountSessionsOnFrame.incrementAndGet();
         } else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
@@ -168,7 +167,7 @@
         log(WHERE_START_TRACING);
     }
 
-    private void onStop(WindowTracingDataSource.Config config) {
+    void onStop(WindowTracingDataSource.Config config) {
         if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
             mCountSessionsOnFrame.decrementAndGet();
         } else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
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/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 4231149..0eafb59 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -42,7 +42,7 @@
                     <xs:annotation name="nonnull"/>
                     <xs:annotation name="final"/>
                 </xs:element>
-                <xs:element type="thermalThrottling" name="thermalThrottling">
+                <xs:element type="thermalThrottling" name="thermalThrottling" minOccurs="0" maxOccurs="1">
                     <xs:annotation name="nonnull"/>
                     <xs:annotation name="final"/>
                 </xs:element>
@@ -464,7 +464,15 @@
             <xs:annotation name="nonnull"/>
             <xs:annotation name="final"/>
         </xs:element>
-        <xs:element name="pollingWindowMillis" type="xs:nonNegativeInteger">
+        <xs:element name="customAnimationRateSec" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1">
+            <xs:annotation name="nonnull"/>
+            <xs:annotation name="final"/>
+        </xs:element>
+        <xs:element name="pollingWindowMaxMillis" type="xs:nonNegativeInteger">
+            <xs:annotation name="nonnull"/>
+            <xs:annotation name="final"/>
+        </xs:element>
+        <xs:element name="pollingWindowMinMillis" type="xs:nonNegativeInteger">
             <xs:annotation name="nonnull"/>
             <xs:annotation name="final"/>
         </xs:element>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index cec2787..355b0ab 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -345,10 +345,14 @@
   public class PowerThrottlingConfig {
     ctor public PowerThrottlingConfig();
     method @NonNull public final java.math.BigDecimal getBrightnessLowestCapAllowed();
-    method @NonNull public final java.math.BigInteger getPollingWindowMillis();
+    method @NonNull public final java.math.BigDecimal getCustomAnimationRateSec();
+    method @NonNull public final java.math.BigInteger getPollingWindowMaxMillis();
+    method @NonNull public final java.math.BigInteger getPollingWindowMinMillis();
     method public final java.util.List<com.android.server.display.config.PowerThrottlingMap> getPowerThrottlingMap();
     method public final void setBrightnessLowestCapAllowed(@NonNull java.math.BigDecimal);
-    method public final void setPollingWindowMillis(@NonNull java.math.BigInteger);
+    method public final void setCustomAnimationRateSec(@NonNull java.math.BigDecimal);
+    method public final void setPollingWindowMaxMillis(@NonNull java.math.BigInteger);
+    method public final void setPollingWindowMinMillis(@NonNull java.math.BigInteger);
   }
 
   public class PowerThrottlingMap {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 6a0dd5a..b982098 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -1325,31 +1325,6 @@
         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()) {
-            pw.print("mUsbDataSignaling=");
-            pw.println(mUsbDataSignalingEnabled);
-        }
-
         pw.print("disableCallerId=");
         pw.println(disableCallerId);
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index a08af72..4beb6a8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -230,11 +230,9 @@
 
         synchronized (mLock) {
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
-            if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-                if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
-                        policyDefinition, userId)) {
-                    return;
-                }
+            if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
+                    policyDefinition, userId)) {
+                return;
             }
 
             if (policyDefinition.isNonCoexistablePolicy()) {
@@ -354,9 +352,7 @@
             }
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
-            if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-                decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
-            }
+            decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
 
             if (policyDefinition.isNonCoexistablePolicy()) {
                 setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
@@ -500,11 +496,9 @@
 
         synchronized (mLock) {
             PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
-            if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-                if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
-                        policyDefinition, UserHandle.USER_ALL)) {
-                    return;
-                }
+            if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
+                    policyDefinition, UserHandle.USER_ALL)) {
+                return;
             }
             // TODO(b/270999567): Move error handling for DISALLOW_CELLULAR_2G into the code
             //  that honors the restriction once there's an API available
@@ -571,9 +565,7 @@
         synchronized (mLock) {
             PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
 
-            if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-                decreasePolicySizeForAdmin(policyState, enforcingAdmin);
-            }
+            decreasePolicySizeForAdmin(policyState, enforcingAdmin);
 
             boolean policyChanged = policyState.removePolicy(enforcingAdmin);
 
@@ -1739,25 +1731,23 @@
                 pw.println();
             }
             pw.decreaseIndent();
-            if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-                pw.println();
+            pw.println();
 
-                pw.println("Default admin policy size limit: " + DEFAULT_POLICY_SIZE_LIMIT);
-                pw.println("Current admin policy size limit: " + mPolicySizeLimit);
-                pw.println("Admin Policies size: ");
-                for (int i = 0; i < mAdminPolicySize.size(); i++) {
-                    int userId = mAdminPolicySize.keyAt(i);
-                    pw.printf("User %d:\n", userId);
-                    pw.increaseIndent();
-                    for (EnforcingAdmin admin : mAdminPolicySize.get(userId).keySet()) {
-                        pw.printf("Admin : " + admin + " : " + mAdminPolicySize.get(userId).get(
-                                admin));
-                        pw.println();
-                    }
-                    pw.decreaseIndent();
+            pw.println("Default admin policy size limit: " + DEFAULT_POLICY_SIZE_LIMIT);
+            pw.println("Current admin policy size limit: " + mPolicySizeLimit);
+            pw.println("Admin Policies size: ");
+            for (int i = 0; i < mAdminPolicySize.size(); i++) {
+                int userId = mAdminPolicySize.keyAt(i);
+                pw.printf("User %d:\n", userId);
+                pw.increaseIndent();
+                for (EnforcingAdmin admin : mAdminPolicySize.get(userId).keySet()) {
+                    pw.printf("Admin : " + admin + " : " + mAdminPolicySize.get(userId).get(
+                            admin));
+                    pw.println();
                 }
                 pw.decreaseIndent();
             }
+            pw.decreaseIndent();
         }
     }
 
@@ -2018,23 +2008,21 @@
 
         private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
                 throws IOException {
-            if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-                if (mAdminPolicySize != null) {
-                    for (int i = 0; i < mAdminPolicySize.size(); i++) {
-                        int userId = mAdminPolicySize.keyAt(i);
-                        for (EnforcingAdmin admin : mAdminPolicySize.get(
-                                userId).keySet()) {
-                            serializer.startTag(/* namespace= */ null,
-                                    TAG_ENFORCING_ADMIN_AND_SIZE);
-                            serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN);
-                            admin.saveToXml(serializer);
-                            serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN);
-                            serializer.startTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE);
-                            serializer.attributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE,
-                                    mAdminPolicySize.get(userId).get(admin));
-                            serializer.endTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE);
-                            serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_AND_SIZE);
-                        }
+            if (mAdminPolicySize != null) {
+                for (int i = 0; i < mAdminPolicySize.size(); i++) {
+                    int userId = mAdminPolicySize.keyAt(i);
+                    for (EnforcingAdmin admin : mAdminPolicySize.get(
+                            userId).keySet()) {
+                        serializer.startTag(/* namespace= */ null,
+                                TAG_ENFORCING_ADMIN_AND_SIZE);
+                        serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN);
+                        admin.saveToXml(serializer);
+                        serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN);
+                        serializer.startTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE);
+                        serializer.attributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE,
+                                mAdminPolicySize.get(userId).get(admin));
+                        serializer.endTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE);
+                        serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_AND_SIZE);
                     }
                 }
             }
@@ -2042,9 +2030,6 @@
 
         private void writeMaxPolicySizeInner(TypedXmlSerializer serializer)
                 throws IOException {
-            if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-                return;
-            }
             serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT);
             serializer.attributeInt(
                     /* namespace= */ null, ATTR_POLICY_SUM_SIZE, mPolicySizeLimit);
@@ -2192,9 +2177,6 @@
 
         private void readMaxPolicySizeInner(TypedXmlPullParser parser)
                 throws XmlPullParserException, IOException {
-            if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-                return;
-            }
             mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d5013517..470025a 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
@@ -1318,9 +1328,7 @@
                 Bundle prevRestrictions) {
             resetCrossProfileIntentFiltersIfNeeded(userId, newRestrictions, prevRestrictions);
             resetUserVpnIfNeeded(userId, newRestrictions, prevRestrictions);
-            if (Flags.deletePrivateSpaceUnderRestriction()) {
-                removePrivateSpaceIfRestrictionIsSet(userId, newRestrictions, prevRestrictions);
-            }
+            removePrivateSpaceIfRestrictionIsSet(userId, newRestrictions, prevRestrictions);
         }
 
         private void resetUserVpnIfNeeded(
@@ -1959,6 +1967,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 +2738,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 +3403,7 @@
 
     @GuardedBy("getLockObject()")
     private void maybeMigrateSecurityLoggingPolicyLocked() {
-        if (!Flags.securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) {
+        if (mOwners.isSecurityLoggingMigrated()) {
             return;
         }
 
@@ -3689,9 +3693,6 @@
             }
 
             revertTransferOwnershipIfNecessaryLocked();
-            if (!Flags.policyEngineMigrationV2Enabled()) {
-                updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
-            }
         }
 
         // Check whether work apps were paused via suspension and unsuspend if necessary.
@@ -7150,9 +7151,7 @@
 
         // If there is a profile owner, redirect to that; otherwise query the device owner.
         ComponentName aliasChooser = getProfileOwnerAsUser(caller.getUserId());
-        boolean isDoUser = Flags.headlessSingleUserFixes()
-                ? caller.getUserId() == getDeviceOwnerUserId()
-                : caller.getUserHandle().isSystem();
+        boolean isDoUser = caller.getUserId() == getDeviceOwnerUserId();
         if (aliasChooser == null && isDoUser) {
             synchronized (getLockObject()) {
                 final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked();
@@ -8162,7 +8161,7 @@
             // First check whether the admin is allowed to wipe the device/user/profile.
             final String restriction;
             boolean shouldFactoryReset = userId == UserHandle.USER_SYSTEM;
-            if (Flags.headlessSingleUserFixes() && getHeadlessDeviceOwnerModeForDeviceOwner()
+            if (getHeadlessDeviceOwnerModeForDeviceOwner()
                     == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) {
                 shouldFactoryReset = userId == getMainUserId();
             }
@@ -8186,8 +8185,7 @@
                 adminPackage,
                 userId)) {
             // Legacy mode
-            wipeDevice = Flags.headlessSingleUserFixes()
-                    && getHeadlessDeviceOwnerModeForDeviceOwner()
+            wipeDevice = getHeadlessDeviceOwnerModeForDeviceOwner()
                     == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER ? isMainUser : isSystemUser;
         } else {
             // Explicit behaviour
@@ -9371,8 +9369,7 @@
 
     void sendDeviceOwnerOrProfileOwnerCommand(String action, Bundle extras, int userId) {
         if (userId == UserHandle.USER_ALL) {
-            if (Flags.headlessDeviceOwnerDelegateSecurityLoggingBugFix()
-                    && getHeadlessDeviceOwnerModeForDeviceOwner()
+            if (getHeadlessDeviceOwnerModeForDeviceOwner()
                     == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) {
                 userId = mOwners.getDeviceOwnerUserId();
             } else {
@@ -11487,10 +11484,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();
@@ -11860,7 +11855,7 @@
             }
             setBackwardsCompatibleAppRestrictions(
                     caller, packageName, restrictions, caller.getUserHandle());
-        } else if (Flags.dmrhSetAppRestrictions()) {
+        } else {
             final boolean isRoleHolder;
             if (who != null) {
                 // DO or PO
@@ -11907,15 +11902,6 @@
                             caller.getUserHandle());
                 });
             }
-        } else {
-            Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
-                    || (caller.hasPackage() && isCallerDelegate(caller,
-                    DELEGATION_APP_RESTRICTIONS)));
-            mInjector.binderWithCleanCallingIdentity(() -> {
-                mUserManager.setApplicationRestrictions(packageName, restrictions,
-                        caller.getUserHandle());
-            });
         }
 
         DevicePolicyEventLogger
@@ -12448,12 +12434,6 @@
         }
 
         if (packageList != null) {
-            if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-                for (String pkg : packageList) {
-                    PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
-                }
-            }
-
             List<InputMethodInfo> enabledImes = mInjector.binderWithCleanCallingIdentity(() ->
                     InputMethodManagerInternal.get().getEnabledInputMethodListAsUser(userId));
             if (enabledImes != null) {
@@ -12690,14 +12670,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(),
@@ -13254,7 +13232,7 @@
                 return Bundle.EMPTY;
             }
             return policies.get(enforcingAdmin).getValue();
-        } else if (Flags.dmrhSetAppRestrictions()) {
+        } else {
             final boolean isRoleHolder;
             if (who != null) {
                 // Caller is DO or PO. They cannot call this on parent
@@ -13297,19 +13275,6 @@
                     return bundle != null ? bundle : Bundle.EMPTY;
                 });
             }
-
-        } else {
-            Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
-                    || (caller.hasPackage() && isCallerDelegate(caller,
-                    DELEGATION_APP_RESTRICTIONS)));
-            return mInjector.binderWithCleanCallingIdentity(() -> {
-                Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
-                        caller.getUserHandle());
-                // if no restrictions were saved, mUserManager.getApplicationRestrictions
-                // returns null, but DPM method should return an empty Bundle as per JavaDoc
-                return bundle != null ? bundle : Bundle.EMPTY;
-            });
         }
     }
 
@@ -13984,11 +13949,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(
@@ -14320,10 +14283,6 @@
             return;
         }
 
-        if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            PolicySizeVerifier.enforceMaxStringLength(accountType, "account type");
-        }
-
         CallerIdentity caller = getCallerIdentity(who, callerPackageName);
         synchronized (getLockObject()) {
             int affectedUser = getAffectedUser(parent);
@@ -14934,11 +14893,6 @@
     public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages)
             throws SecurityException {
         Objects.requireNonNull(packages, "packages is null");
-        if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            for (String pkg : packages) {
-                PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
-            }
-        }
 
         CallerIdentity caller = getCallerIdentity(who, callerPackageName);
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES);
@@ -15219,7 +15173,7 @@
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(
                 isProfileOwner(caller) || isDefaultDeviceOwner(caller));
-        if (Flags.allowScreenBrightnessControlOnCope() && parent) {
+        if (parent) {
             Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
         }
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING);
@@ -15230,7 +15184,7 @@
                         "Permission denial: device owners cannot update %1$s", setting));
             }
             int affectedUser;
-            if (Flags.allowScreenBrightnessControlOnCope() && parent) {
+            if (parent) {
                 affectedUser = getProfileParentId(caller.getUserId());
             } else {
                 affectedUser = caller.getUserId();
@@ -16304,9 +16258,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 +16265,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);
@@ -16828,13 +16776,11 @@
                     mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
                 }
 
-                if (Flags.permissionMigrationForZeroTrustImplEnabled()) {
-                    final UserHandle user = UserHandle.of(userId);
-                    final String roleHolderPackage = getRoleHolderPackageNameOnUser(
-                            RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, userId);
-                    if (roleHolderPackage != null) {
-                        broadcastExplicitIntentToPackage(intent, roleHolderPackage, user);
-                    }
+                final UserHandle user = UserHandle.of(userId);
+                final String roleHolderPackage = getRoleHolderPackageNameOnUser(
+                        RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, userId);
+                if (roleHolderPackage != null) {
+                    broadcastExplicitIntentToPackage(intent, roleHolderPackage, user);
                 }
             }
         });
@@ -16842,18 +16788,10 @@
 
     @Override
     public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin, String callerPackage) {
-        if (Flags.permissionMigrationForZeroTrustImplEnabled()) {
-            CallerIdentity caller = getCallerIdentity(admin, callerPackage);
-            enforcePermissions(new String[] {NOTIFY_PENDING_SYSTEM_UPDATE,
-                    MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}, caller.getPackageName(),
-                    caller.getUserId());
-        } else {
-            Objects.requireNonNull(admin, "ComponentName is null");
-
-            final CallerIdentity caller = getCallerIdentity(admin);
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(admin, callerPackage);
+        enforcePermissions(new String[] {NOTIFY_PENDING_SYSTEM_UPDATE,
+                MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}, caller.getPackageName(),
+                caller.getUserId());
         return mOwners.getSystemUpdateInfo();
     }
 
@@ -17329,7 +17267,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;
@@ -17397,17 +17335,10 @@
             @Nullable ComponentName componentName, @UserIdInt int callingUserId) {
         synchronized (getLockObject()) {
             int deviceOwnerUserId = -1;
-            if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
-                deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
-                        && getHeadlessDeviceOwnerModeForDeviceAdmin(componentName, callingUserId)
-                        == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED
-                        ? UserHandle.USER_SYSTEM : callingUserId;
-            } else {
-                deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
-                        && getHeadlessDeviceOwnerModeForDeviceOwner()
-                        == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED
-                        ? UserHandle.USER_SYSTEM : callingUserId;
-            }
+            deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+                    && getHeadlessDeviceOwnerModeForDeviceAdmin(componentName, callingUserId)
+                    == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED
+                    ? UserHandle.USER_SYSTEM : callingUserId;
             Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d",
                     callingUserId, deviceOwnerUserId);
             // hasIncompatibleAccountsOrNonAdb doesn't matter since the caller is not adb.
@@ -17920,15 +17851,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 +18196,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 +18231,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 +18314,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 +18351,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 +18375,6 @@
             return false;
         }
 
-        if (!Flags.securityLogV2Enabled()) {
-            throw new UnsupportedOperationException("Audit log not enabled");
-        }
-
         final CallerIdentity caller = getCallerIdentity(callingPackage);
         EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
                 null /* admin */,
@@ -18759,8 +18637,7 @@
 
         // Backup service has to be enabled on the main user in order for it to be enabled on
         // secondary users.
-        if (Flags.headlessSingleUserFixes() && isDeviceOwner(caller)
-                && getHeadlessDeviceOwnerModeForDeviceOwner()
+        if (isDeviceOwner(caller) && getHeadlessDeviceOwnerModeForDeviceOwner()
                 == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER) {
             toggleBackupServiceActive(UserHandle.USER_SYSTEM, enabled);
         }
@@ -19839,16 +19716,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 +20621,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);
                     }
                 }
@@ -21505,13 +21378,7 @@
 
         final CallerIdentity caller = getCallerIdentity(callerPackage);
 
-        if (Flags.permissionMigrationForZeroTrustImplEnabled()) {
-            enforcePermission(MANAGE_DEVICE_POLICY_CERTIFICATES, caller.getPackageName());
-        } else {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwner(caller)
-                            || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
-        }
+        enforcePermission(MANAGE_DEVICE_POLICY_CERTIFICATES, caller.getPackageName());
         synchronized (getLockObject()) {
             final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
                     caller.getUserId());
@@ -21839,8 +21706,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 +21717,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);
                 }
@@ -22112,16 +21977,9 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             boolean isSingleUserMode;
-            if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
-                int headlessDeviceOwnerMode = getHeadlessDeviceOwnerModeForDeviceAdmin(
-                        deviceAdmin, caller.getUserId());
-                isSingleUserMode =
-                        headlessDeviceOwnerMode == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
-            } else {
-                isSingleUserMode =
-                        getHeadlessDeviceOwnerModeForDeviceOwner()
-                                == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
-            }
+            int headlessDeviceOwnerMode = getHeadlessDeviceOwnerModeForDeviceAdmin(
+                    deviceAdmin, caller.getUserId());
+            isSingleUserMode = headlessDeviceOwnerMode == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
 
             if (Flags.headlessSingleMinTargetSdk()
                     && mInjector.userManagerIsHeadlessSystemUserMode()
@@ -22145,8 +22003,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(
@@ -22520,35 +22378,17 @@
         Objects.requireNonNull(packageName, "Admin package name must be provided");
         final CallerIdentity caller = getCallerIdentity(packageName);
 
-        if (!Flags.policyEngineMigrationV2Enabled()) {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
-                    "USB data signaling can only be controlled by a device owner or "
-                            + "a profile owner on an organization-owned device.");
+        synchronized (getLockObject()) {
+            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                    /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
+                    caller.getPackageName(),
+                    caller.getUserId());
             Preconditions.checkState(canUsbDataSignalingBeDisabled(),
                     "USB data signaling cannot be disabled.");
-        }
-
-        synchronized (getLockObject()) {
-            if (Flags.policyEngineMigrationV2Enabled()) {
-                EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                        /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
-                        caller.getPackageName(),
-                        caller.getUserId());
-                Preconditions.checkState(canUsbDataSignalingBeDisabled(),
-                        "USB data signaling cannot be disabled.");
-                mDevicePolicyEngine.setGlobalPolicy(
-                        PolicyDefinition.USB_DATA_SIGNALING,
-                        enforcingAdmin,
-                        new BooleanPolicyValue(enabled));
-            } else {
-                ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-                if (admin.mUsbDataSignalingEnabled != enabled) {
-                    admin.mUsbDataSignalingEnabled = enabled;
-                    saveSettingsLocked(caller.getUserId());
-                    updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
-                }
-            }
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.USB_DATA_SIGNALING,
+                    enforcingAdmin,
+                    new BooleanPolicyValue(enabled));
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_USB_DATA_SIGNALING)
@@ -22570,24 +22410,10 @@
     @Override
     public boolean isUsbDataSignalingEnabled(String packageName) {
         final CallerIdentity caller = getCallerIdentity(packageName);
-        if (Flags.policyEngineMigrationV2Enabled()) {
-            Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
-                    PolicyDefinition.USB_DATA_SIGNALING,
-                    caller.getUserId());
-            return enabled == null || enabled;
-        } else {
-            synchronized (getLockObject()) {
-                // If the caller is an admin, return the policy set by itself. Otherwise
-                // return the device-wide policy.
-                if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(
-                        caller)) {
-                    return getProfileOwnerOrDeviceOwnerLocked(
-                            caller.getUserId()).mUsbDataSignalingEnabled;
-                } else {
-                    return isUsbDataSignalingEnabledInternalLocked();
-                }
-            }
-        }
+        Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
+                PolicyDefinition.USB_DATA_SIGNALING,
+                caller.getUserId());
+        return enabled == null || enabled;
     }
 
     private boolean isUsbDataSignalingEnabledInternalLocked() {
@@ -23501,6 +23327,8 @@
                 MANAGE_DEVICE_POLICY_ACROSS_USERS);
         CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIPE_DATA,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS);
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS);
 
         // These permissions may grant access to user data and therefore must be protected with
         // MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL for cross-user calls.
@@ -24136,15 +23964,13 @@
 
     @Override
     public @ContentProtectionPolicy int getContentProtectionPolicy(
-            ComponentName who, String callerPackageName) {
+            ComponentName who, String callerPackageName, int userId) {
         if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) {
             return CONTENT_PROTECTION_DISABLED;
         }
 
         CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-        int userId = caller.getUserId();
         enforceCanQuery(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, callerPackageName, userId);
-
         Integer policy =
                 mDevicePolicyEngine.getResolvedPolicy(PolicyDefinition.CONTENT_PROTECTION, userId);
         if (policy == null) {
@@ -24940,9 +24766,6 @@
 
     @Override
     public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
-        if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            return;
-        }
         CallerIdentity caller = getCallerIdentity(callerPackageName);
         enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
                 caller.getUserId());
@@ -24956,9 +24779,6 @@
 
     @Override
     public int getMaxPolicyStorageLimit(String callerPackageName) {
-        if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            return -1;
-        }
         CallerIdentity caller = getCallerIdentity(callerPackageName);
         enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
                 caller.getUserId());
@@ -24968,9 +24788,6 @@
 
     @Override
     public void forceSetMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
-        if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            return;
-        }
         CallerIdentity caller = getCallerIdentity(callerPackageName);
         enforcePermission(MANAGE_DEVICE_POLICY_STORAGE_LIMIT, caller.getPackageName(),
                 caller.getUserId());
@@ -24981,9 +24798,6 @@
     @Override
     public int getPolicySizeForAdmin(
             String callerPackageName, android.app.admin.EnforcingAdmin admin) {
-        if (!Flags.devicePolicySizeTrackingInternalBugFixEnabled()) {
-            return -1;
-        }
         CallerIdentity caller = getCallerIdentity(callerPackageName);
         enforcePermission(MANAGE_DEVICE_POLICY_STORAGE_LIMIT, caller.getPackageName(),
                 caller.getUserId());
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..ab459df 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,7 +16,6 @@
 
 package com.android.server;
 
-import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
@@ -38,6 +37,7 @@
 import android.app.INotificationManager;
 import android.app.SystemServiceRegistry;
 import android.app.admin.DevicePolicySafetyChecker;
+import android.app.appfunctions.AppFunctionManagerConfiguration;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -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);
@@ -1729,12 +1743,11 @@
             mSystemServiceManager.startService(LogcatManagerService.class);
             t.traceEnd();
 
-            t.traceBegin("StartAppFunctionManager");
-            if (enableAppFunctionManager()) {
+            if (AppFunctionManagerConfiguration.isSupported(context)) {
+                t.traceBegin("StartAppFunctionManager");
                 mSystemServiceManager.startService(AppFunctionManagerService.class);
+                t.traceEnd();
             }
-            t.traceEnd();
-
         } catch (Throwable e) {
             Slog.e("System", "******************************************");
             Slog.e("System", "************ Failure starting core service");
@@ -2454,8 +2467,8 @@
                 reportWtf("starting RuntimeService", e);
             }
             t.traceEnd();
-
-            if (!isWatch && !disableNetworkTime) {
+            if (!disableNetworkTime && (!isWatch || (isWatch
+                    && android.server.Flags.allowNetworkTimeUpdateService()))) {
                 t.traceBegin("StartNetworkTimeUpdateService");
                 try {
                     networkTimeUpdater = new NetworkTimeUpdateService(context);
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
index 29f3871..ec74ef19 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -28,4 +28,11 @@
      namespace: "wear_frameworks"
      description: "Allow removing VpnManagerService"
      bug: "340928692"
+}
+
+flag {
+    name: "allow_network_time_update_service"
+    namespace: "wear_systems"
+    description: "Allow NetworkTimeUpdateService on Wear"
+    bug: "327508176"
 }
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 7ed23cd..d2c91ff 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -888,7 +888,7 @@
                     val mayGrantByPrivileged =
                         !permission.isPrivileged ||
                             requestingPackageStates.anyIndexed { _, it ->
-                                checkPrivilegedPermissionAllowlist(it, permission)
+                                checkPrivilegedPermissionAllowlistIfNeeded(it, permission)
                             }
                     val shouldGrantBySignature =
                         permission.isSignature &&
@@ -1280,7 +1280,16 @@
         }
     }
 
-    private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
+    /**
+     * We only check privileged permission allowlist for system privileged apps. Hence, for platform
+     * or for normal apps, we return true to indicate that we don't need to check the allowlist and
+     * will let follow-up checks to decide whether we should grant the permission.
+     *
+     * @return `true`, if the permission is allowlisted for system privileged apps, or if we
+     *         don't need to check the allowlist (for platform or for normal apps).
+     *         `false`, if the permission is not allowlisted for system privileged apps.
+     */
+    private fun MutateStateScope.checkPrivilegedPermissionAllowlistIfNeeded(
         packageState: PackageState,
         permission: Permission
     ): Boolean {
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/OWNERS b/services/supervision/OWNERS
new file mode 100644
index 0000000..e5f4147
--- /dev/null
+++ b/services/supervision/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/app/supervision/OWNERS
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..7ffd0ec
--- /dev/null
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+
+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
+    public void onShellCommand(
+            @Nullable FileDescriptor in,
+            @Nullable FileDescriptor out,
+            @Nullable FileDescriptor err,
+            @NonNull String[] args,
+            @Nullable ShellCallback callback,
+            @NonNull ResultReceiver resultReceiver) throws RemoteException {
+        new SupervisionServiceShellCommand(this)
+                .exec(this, in, out, err, args, callback, resultReceiver);
+    }
+
+    @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/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
new file mode 100644
index 0000000..3aba24a
--- /dev/null
+++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java
@@ -0,0 +1,61 @@
+/*
+ * 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.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+public class SupervisionServiceShellCommand extends ShellCommand {
+    private final SupervisionService mService;
+
+    public SupervisionServiceShellCommand(SupervisionService mService) {
+        this.mService = mService;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(null);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch (cmd) {
+            case "help": return help(pw);
+            case "is-enabled": return isEnabled(pw);
+            default: return handleDefaultCommands(cmd);
+        }
+    }
+
+    private int help(PrintWriter pw) {
+        pw.println("Supervision service commands:");
+        pw.println("  help");
+        pw.println("      Prints this help text");
+        pw.println("  is-enabled");
+        pw.println("      Is supervision enabled");
+        return 0;
+    }
+
+    private int isEnabled(PrintWriter pw) {
+        pw.println(mService.isSupervisionEnabled());
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        help(getOutPrintWriter());
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 3bce9b5..9044259 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -138,3 +138,17 @@
         enabled: false,
     },
 }
+
+test_module_config {
+    name: "FrameworksInputMethodSystemServerTests_server_inputmethod",
+    base: "FrameworksInputMethodSystemServerTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.inputmethod"],
+}
+
+test_module_config {
+    name: "FrameworksImeTests_android_inputmethodservice",
+    base: "FrameworksImeTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.inputmethodservice"],
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 0787058..2c78504 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -33,11 +33,15 @@
 import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.WindowManagerGlobal;
 import android.view.WindowManagerPolicyConstants;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodManager;
 
 import androidx.annotation.NonNull;
@@ -56,6 +60,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -89,6 +94,9 @@
     private String mInputMethodId;
     private boolean mShowImeWithHardKeyboardEnabled;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -155,7 +163,13 @@
                 () -> assertThat(mUiDevice.pressHome()).isTrue(),
                 true /* expected */,
                 false /* inputViewStarted */);
-        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+        if (Flags.refactorInsetsController()) {
+            // The IME visibility is only sent at the end of the animation. Therefore, we have to
+            // wait until the visibility was sent to the server and the IME window hidden.
+            eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+        } else {
+            assertThat(mInputMethodService.isInputViewShown()).isFalse();
+        }
     }
 
     /**
@@ -182,8 +196,13 @@
 
     /**
      * This checks the result of calling IMS#requestShowSelf and IMS#requestHideSelf.
+     *
+     * With the refactor in b/298172246, all calls to IMMS#{show,hide}MySoftInputLocked
+     * will be just apply the requested visibility (by using the callback). Therefore, we will
+     * lose flags like HIDE_IMPLICIT_ONLY.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testShowHideSelf() throws Exception {
         setShowImeWithHardKeyboard(true /* enabled */);
 
@@ -375,8 +394,13 @@
     /**
      * This checks that an implicit show request when the IME is not previously shown,
      * and it should be shown in fullscreen mode, results in the IME not being shown.
+     *
+     * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+     * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+     * SHOW_IMPLICIT.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testShowSoftInputImplicitly_fullScreenMode() throws Exception {
         setShowImeWithHardKeyboard(true /* enabled */);
 
@@ -425,8 +449,13 @@
     /**
      * This checks that an implicit show request when a hard keyboard is connected,
      * results in the IME not being shown.
+     *
+     * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+     * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+     * SHOW_IMPLICIT.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testShowSoftInputImplicitly_withHardKeyboard() throws Exception {
         setShowImeWithHardKeyboard(false /* enabled */);
 
@@ -484,8 +513,13 @@
      * This checks that an implicit show request followed by connecting a hard keyboard
      * and a configuration change, does not trigger IMS#onFinishInputView,
      * but results in the IME being hidden.
+     *
+     * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+     * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+     * SHOW_IMPLICIT.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testShowSoftInputImplicitly_thenConfigurationChanged() throws Exception {
         setShowImeWithHardKeyboard(false /* enabled */);
 
@@ -567,8 +601,13 @@
      * This checks that a forced show request directly followed by an explicit show request,
      * and then a hide not always request, still results in the IME being shown
      * (i.e. the explicit show request retains the forced state).
+     *
+     * With the refactor in b/298172246, all calls from InputMethodManager#{show,hide}SoftInput
+     * will be redirected to InsetsController#{show,hide}. Therefore, we will lose flags like
+     * HIDE_NOT_ALWAYS.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testShowSoftInputForced_testShowSoftInputExplicitly_thenHideSoftInputNotAlways()
             throws Exception {
         setShowImeWithHardKeyboard(true /* enabled */);
@@ -734,7 +773,13 @@
         backButtonUiObject.click();
         mInstrumentation.waitForIdleSync();
 
-        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+        if (Flags.refactorInsetsController()) {
+            // The IME visibility is only sent at the end of the animation. Therefore, we have to
+            // wait until the visibility was sent to the server and the IME window hidden.
+            eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+        } else {
+            assertThat(mInputMethodService.isInputViewShown()).isFalse();
+        }
     }
 
     /**
@@ -766,7 +811,13 @@
         backButtonUiObject.longClick();
         mInstrumentation.waitForIdleSync();
 
-        assertThat(mInputMethodService.isInputViewShown()).isFalse();
+        if (Flags.refactorInsetsController()) {
+            // The IME visibility is only sent at the end of the animation. Therefore, we have to
+            // wait until the visibility was sent to the server and the IME window hidden.
+            eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+        } else {
+            assertThat(mInputMethodService.isInputViewShown()).isFalse();
+        }
     }
 
     /**
@@ -848,7 +899,13 @@
         assertWithMessage("Input Method Switcher Menu is shown")
                 .that(isInputMethodPickerShown(imm))
                 .isTrue();
-        assertThat(mInputMethodService.isInputViewShown()).isTrue();
+        if (Flags.refactorInsetsController()) {
+            // The IME visibility is only sent at the end of the animation. Therefore, we have to
+            // wait until the visibility was sent to the server and the IME window hidden.
+            eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+        } else {
+            assertThat(mInputMethodService.isInputViewShown()).isTrue();
+        }
 
         // Hide the Picker menu before finishing.
         mUiDevice.pressBack();
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index 6fd21f7..75db316 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -88,3 +88,10 @@
         " && $(location soong_zip) -o $(genDir)/temp.apk -L 0 -C $(genDir)/apk -D $(genDir)/apk" +
         " && $(location zipalign) -f 4 $(genDir)/temp.apk $(out)",
 }
+
+test_module_config_host {
+    name: "PackageManagerServiceHostTests_test_overlayactorvisibilitytest",
+    base: "PackageManagerServiceHostTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.pm.test.OverlayActorVisibilityTest"],
+}
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index 598e273..e26213e 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -164,3 +164,25 @@
         "done && " +
         "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
 }
+
+test_module_config {
+    name: "PackageManagerServiceServerTests_server_pm_Presubmit",
+    base: "PackageManagerServiceServerTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.pm."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+}
+
+test_module_config {
+    name: "PackageManagerServiceServerTests_server_pm_Postsubmit",
+    base: "PackageManagerServiceServerTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.pm."],
+    include_annotations: ["android.platform.test.annotations.Postsubmit"],
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index c93f482..db88b16 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -47,3 +47,10 @@
     platform_apis: true,
     test_suites: ["device-tests"],
 }
+
+test_module_config {
+    name: "PackageManagerServiceUnitTests_verify_domain",
+    base: "PackageManagerServiceUnitTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.pm.test.verify.domain"],
+}
diff --git a/services/tests/appfunctions/OWNERS b/services/tests/appfunctions/OWNERS
new file mode 100644
index 0000000..7fa8917
--- /dev/null
+++ b/services/tests/appfunctions/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1627156
+include platform/frameworks/base:/core/java/android/app/appfunctions/OWNERS
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index 3bafe72..61350bf 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -56,3 +56,13 @@
         enabled: false,
     },
 }
+
+test_module_config {
+    name: "DisplayServiceTests_server_display",
+    base: "DisplayServiceTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.display"],
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
index 1f3184d..f589a2c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
@@ -42,31 +42,31 @@
     private val mockToken = mock<IBinder>()
 
     @Test
-    fun `returns HBC max brightness if HBM supported and ON`() {
+    fun testMaxBrightness_HbmSupportedAndOn() {
         val controller = createController()
         assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
     }
 
     @Test
-    fun `returns NBC max brightness if device does not support HBM`() {
+    fun testMaxBrightness_HbmNotSupported() {
         val controller = createController(hbmSupported = false)
         assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
     }
 
     @Test
-    fun `returns NBC max brightness if HBM not allowed`() {
+    fun testMaxBrightness_HbmNotAllowed() {
         val controller = createController(hbmAllowed = false)
         assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
     }
 
     @Test
-    fun `returns HBC max brightness if NBM is disabled`() {
+    fun testMaxBrightness_HbmDisabledAndNotAllowed() {
         val controller = createController(nbmEnabled = false, hbmAllowed = false)
         assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
     }
 
     @Test
-    fun `returns HBC max brightness if lower than NBC max brightness`() {
+    fun testMaxBrightness_transitionPointLessThanCurrentNbmLimit() {
         val controller = createController(
             hbmAllowed = false,
             hbmMaxBrightness = TRANSITION_POINT,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index f690b1b..2d4a29b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.hardware.display.BrightnessInfo;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -112,7 +114,10 @@
                 .append("\n    mBrightnessAdjustmentFlag:")
                 .append(displayBrightnessState.getBrightnessAdjustmentFlag())
                 .append("\n    mIsUserInitiatedChange:")
-                .append(displayBrightnessState.isUserInitiatedChange());
+                .append(displayBrightnessState.isUserInitiatedChange())
+                .append("\n    mBrightnessMaxReason:")
+                .append(BrightnessInfo.briMaxReasonToString(
+                        displayBrightnessState.getBrightnessMaxReason()));
         return sb.toString();
     }
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index d450683..fd05b26 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -263,7 +263,9 @@
                 mDisplayDeviceConfig.getPowerThrottlingConfigData();
         assertNotNull(powerThrottlingConfigData);
         assertEquals(0.1f, powerThrottlingConfigData.brightnessLowestCapAllowed, SMALL_DELTA);
-        assertEquals(10, powerThrottlingConfigData.pollingWindowMillis);
+        assertEquals(15f, powerThrottlingConfigData.customAnimationRateSec, SMALL_DELTA);
+        assertEquals(20000, powerThrottlingConfigData.pollingWindowMaxMillis);
+        assertEquals(10000, powerThrottlingConfigData.pollingWindowMinMillis);
     }
 
     @Test
@@ -1295,7 +1297,9 @@
     private String getPowerThrottlingConfig() {
         return  "<powerThrottlingConfig >\n"
                 +       "<brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed>\n"
-                +       "<pollingWindowMillis>10</pollingWindowMillis>\n"
+                +       "<customAnimationRateSec>15</customAnimationRateSec>\n"
+                +       "<pollingWindowMaxMillis>20000</pollingWindowMaxMillis>\n"
+                +       "<pollingWindowMinMillis>10000</pollingWindowMinMillis>\n"
                 +       "<powerThrottlingMap>\n"
                 +           "<powerThrottlingPoint>\n"
                 +               "<thermalStatus>light</thermalStatus>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index bbf2ecb..5bb8ded 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -64,6 +64,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions.LaunchCookie;
 import android.app.PropertyInvalidatedCache;
 import android.companion.virtual.IVirtualDevice;
@@ -358,6 +359,7 @@
     @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
     @Mock PackageManagerInternal mMockPackageManagerInternal;
     @Mock DisplayManagerInternal mMockDisplayManagerInternal;
+    @Mock ActivityManagerInternal mMockActivityManagerInternal;
     @Mock DisplayAdapter mMockDisplayAdapter;
 
     @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@@ -388,6 +390,8 @@
                 PackageManagerInternal.class, mMockPackageManagerInternal);
         mLocalServiceKeeperRule.overrideLocalService(
                 DisplayManagerInternal.class, mMockDisplayManagerInternal);
+        mLocalServiceKeeperRule.overrideLocalService(
+                ActivityManagerInternal.class, mMockActivityManagerInternal);
         Display display = mock(Display.class);
         when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments());
         when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class));
@@ -2080,6 +2084,31 @@
     }
 
     /**
+     * Tests that the DisplayInfo is updated correctly with a render frame rate even if it not
+     * a divisor of the peak refresh rate.
+     */
+    @Test
+    public void testDisplayInfoRenderFrameRateNonPeakDivisor() {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{120f}, new float[]{240f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(120f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 80f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(80f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
      * Tests that the mode reflects the render frame rate is in compat mode
      */
     @Test
@@ -3348,13 +3377,26 @@
     }
 
     private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
-
                                                       float[] refreshRates) {
         return createFakeDisplayDevice(displayManager, refreshRates, Display.TYPE_UNKNOWN);
     }
 
     private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
                                                       float[] refreshRates,
+                                                      float[] vsyncRates) {
+        return createFakeDisplayDevice(displayManager, refreshRates, vsyncRates,
+                Display.TYPE_UNKNOWN);
+    }
+
+    private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+            float[] refreshRates,
+            int displayType) {
+        return createFakeDisplayDevice(displayManager, refreshRates, refreshRates, displayType);
+    }
+
+    private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+                                                      float[] refreshRates,
+                                                      float[] vsyncRates,
                                                       int displayType) {
         FakeDisplayDevice displayDevice = new FakeDisplayDevice();
         DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
@@ -3363,7 +3405,8 @@
         displayDeviceInfo.supportedModes = new Display.Mode[refreshRates.length];
         for (int i = 0; i < refreshRates.length; i++) {
             displayDeviceInfo.supportedModes[i] =
-                    new Display.Mode(i + 1, width, height, refreshRates[i]);
+                    new Display.Mode(i + 1, width, height, refreshRates[i], vsyncRates[i],
+                            new float[0], new int[0]);
         }
         displayDeviceInfo.modeId = 1;
         displayDeviceInfo.type = displayType;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index 30c384a..5e868a3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -38,7 +38,7 @@
     private DisplayManagerInternal.DisplayOffloader mDisplayOffloader;
 
     @Mock
-    private DisplayPowerControllerInterface mDisplayPowerController;
+    private DisplayPowerController mDisplayPowerController;
 
     private DisplayOffloadSessionImpl mSession;
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 5840cb9..d0aec3b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -2213,6 +2213,20 @@
                 /* ignoreAnimationLimits= */ anyBoolean());
     }
 
+    @Test
+    public void testManualBrightnessModeSavesBrightness() {
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Initialize
+
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+        advanceTime(1);
+
+        verify(mHolder.brightnessSetting).saveIfNeeded();
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
@@ -2573,7 +2587,7 @@
         BrightnessClamperController getBrightnessClamperController(Handler handler,
                 BrightnessClamperController.ClamperChangeListener clamperChangeListener,
                 BrightnessClamperController.DisplayDeviceData data, Context context,
-                DisplayManagerFlags flags, SensorManager sensorManager) {
+                DisplayManagerFlags flags, SensorManager sensorManager, float currentBrightness) {
             return mClamperController;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
index 33d3020..a684fdb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
@@ -60,7 +60,7 @@
     }
 
     @Test
-    fun `destroys ColorFade on stop`() {
+    fun destroysColorFadeOnStop() {
         displayPowerState.stop()
         val runnableCaptor = argumentCaptor<Runnable>()
 
@@ -71,13 +71,13 @@
     }
 
     @Test
-    fun `GIVEN not prepared WHEN draw runnable is called THEN colorFade not drawn`() {
+    fun testColorFadeDraw_notPrepared() {
         displayPowerState.mColorFadeDrawRunnable.run()
 
         verify(mockColorFade, never()).draw(anyFloat())
     }
     @Test
-    fun `GIVEN prepared WHEN draw runnable is called THEN colorFade is drawn`() {
+    fun testColorFadeDraw_prepared() {
         displayPowerState.prepareColorFade(mockContext, ColorFade.MODE_FADE)
         clearInvocations(mockColorFade)
 
@@ -87,7 +87,7 @@
     }
 
     @Test
-    fun `GIVEN prepared AND stopped WHEN draw runnable is called THEN colorFade is not drawn`() {
+    fun testColorFadeDraw_preparedAndStopped() {
         displayPowerState.prepareColorFade(mockContext, ColorFade.MODE_FADE)
         clearInvocations(mockColorFade)
         displayPowerState.stop()
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index a7e0ebd..120cc84 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -118,7 +118,7 @@
     @Mock
     private DisplayManagerFlags mFlags;
     @Mock
-    private DisplayPowerControllerInterface mMockedDisplayPowerController;
+    private DisplayPowerController mMockedDisplayPowerController;
 
     private Handler mHandler;
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 0ce9233..f9dc122 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -31,7 +31,6 @@
 
 import android.content.Context;
 import android.hardware.SensorManager;
-import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -161,12 +160,6 @@
     }
 
     @Test
-    public void testMaxReasonIsNoneOnInit() {
-        assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
-                mClamperController.getBrightnessMaxReason());
-    }
-
-    @Test
     public void testOnDisplayChanged_DelegatesToClamper() {
         mClamperController.onDisplayChanged(mMockDisplayDeviceData);
 
@@ -365,7 +358,7 @@
 
     private BrightnessClamperController createBrightnessClamperController() {
         return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener,
-                mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager);
+                mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager, 0);
     }
 
     interface TestDisplayListenerModifier extends BrightnessStateModifier,
@@ -403,7 +396,7 @@
                 Handler handler,
                 BrightnessClamperController.ClamperChangeListener clamperChangeListener,
                 BrightnessClamperController.DisplayDeviceData data,
-                DisplayManagerFlags flags, Context context) {
+                DisplayManagerFlags flags, Context context, float currentBrightness) {
             mCapturedChangeListener = clamperChangeListener;
             return mClampers;
         }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
index b3f33ad..c4898da 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
+import android.os.IThermalService;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.Temperature;
@@ -58,12 +59,18 @@
     private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
             new FakeDeviceConfigInterface();
     private final TestHandler mTestHandler = new TestHandler(null);
+    private final TestInjector mTestInjector = new TestInjector();
     private BrightnessPowerClamper mClamper;
+    private final float mCurrentBrightness = 0.6f;
+    private PowerChangeListener mPowerChangeListener;
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mClamper = new BrightnessPowerClamper(new TestInjector(), mTestHandler,
-                mMockClamperChangeListener, new TestPowerData());
+        mClamper = new BrightnessPowerClamper(mTestInjector, mTestHandler,
+                mMockClamperChangeListener, new TestPowerData(), mCurrentBrightness);
+        mPowerChangeListener = mClamper.getPowerChangeListener();
+        mPmicMonitor = mTestInjector.getPmicMonitor(mPowerChangeListener, null, 5, 10);
+        mPmicMonitor.setPowerChangeListener(mPowerChangeListener);
         mTestHandler.flush();
     }
 
@@ -79,36 +86,27 @@
     }
 
     @Test
-    public void testPowerThrottlingNoOngoingAnimation() throws RemoteException {
-        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
+    public void testPowerThrottlingWithThermalLevelLight() throws RemoteException {
+        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT);
         mTestHandler.flush();
         assertFalse(mClamper.isActive());
         assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
 
         // update a new device config for power-throttling.
         mClamper.onDisplayChanged(new TestPowerData(
-                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f))));
+                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f))));
 
         mPmicMonitor.setAvgPowerConsumed(200f);
         float expectedBrightness = 0.5f;
-        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
+        expectedBrightness = expectedBrightness * mCurrentBrightness;
 
         mTestHandler.flush();
         // Assume current brightness as max, as there is no throttling.
         assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
-        // update a new device config for power-throttling.
-        mClamper.onDisplayChanged(new TestPowerData(
-                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
-
-        mPmicMonitor.setAvgPowerConsumed(100f);
-        expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
-        mTestHandler.flush();
-        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
     }
 
     @Test
-    public void testPowerThrottlingWithOngoingAnimation() throws RemoteException {
+    public void testPowerThrottlingWithThermalLevelSevere() throws RemoteException {
         mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE);
         mTestHandler.flush();
         assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
@@ -119,20 +117,10 @@
 
         mPmicMonitor.setAvgPowerConsumed(200f);
         float expectedBrightness = 0.5f;
-        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
-
+        expectedBrightness = expectedBrightness * mCurrentBrightness;
         mTestHandler.flush();
         // Assume current brightness as max, as there is no throttling.
         assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
-        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL);
-        // update a new device config for power-throttling.
-        mClamper.onDisplayChanged(new TestPowerData(
-                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f))));
-
-        mPmicMonitor.setAvgPowerConsumed(100f);
-        expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX;
-        mTestHandler.flush();
-        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -148,8 +136,7 @@
 
         mPmicMonitor.setAvgPowerConsumed(200f);
         float expectedBrightness = 0.5f;
-        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX;
-
+        expectedBrightness = expectedBrightness * mCurrentBrightness;
         mTestHandler.flush();
 
         assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
@@ -169,10 +156,11 @@
 
     private static class TestPmicMonitor extends PmicMonitor {
         private Temperature mCurrentTemperature;
-        private final PowerChangeListener mListener;
-        TestPmicMonitor(PowerChangeListener listener, int pollingTime) {
-            super(listener, pollingTime);
-            mListener = listener;
+        private PowerChangeListener mListener;
+        TestPmicMonitor(PowerChangeListener listener,
+                        IThermalService thermalService,
+                        int pollingTimeMax, int pollingTimeMin) {
+            super(listener, thermalService, pollingTimeMax, pollingTimeMin);
         }
         public void setAvgPowerConsumed(float power) {
             int status = mCurrentTemperature.getStatus();
@@ -181,13 +169,18 @@
         public void setThermalStatus(@Temperature.ThrottlingStatus int status) {
             mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status);
         }
+        public void setPowerChangeListener(PowerChangeListener listener) {
+            mListener = listener;
+        }
     }
 
     private class TestInjector extends BrightnessPowerClamper.Injector {
         @Override
         TestPmicMonitor getPmicMonitor(PowerChangeListener listener,
-                int pollingTime) {
-            mPmicMonitor = new TestPmicMonitor(listener, pollingTime);
+                                       IThermalService thermalService,
+                                       int minPollingTimeMillis, int maxPollingTimeMillis) {
+            mPmicMonitor = new TestPmicMonitor(listener, thermalService, maxPollingTimeMillis,
+                    minPollingTimeMillis);
             return mPmicMonitor;
         }
 
@@ -216,7 +209,7 @@
             mUniqueDisplayId = uniqueDisplayId;
             mDataId = dataId;
             mData = PowerThrottlingData.create(data);
-            mConfigData = new PowerThrottlingConfigData(0.1f, 10);
+            mConfigData = new PowerThrottlingConfigData(0.1f, 10, 20, 10);
         }
 
         @NonNull
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
index 34c6ba9..1f3f19f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
@@ -50,7 +50,7 @@
     }
 
     @Test
-    fun `test app request votes`(@TestParameter testCase: AppRequestTestCase) {
+    fun testAppRequestVotes(@TestParameter testCase: AppRequestTestCase) {
         whenever(mockFlags.ignoreAppPreferredRefreshRateRequest())
                 .thenReturn(testCase.ignoreRefreshRateRequest)
         val displayModeDirector = DisplayModeDirector(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt
index bf2edfe..3841211 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BaseModeRefreshRateVoteTest.kt
@@ -39,7 +39,7 @@
     }
 
     @Test
-    fun `updates summary with base mode refresh rate if not set`() {
+    fun updatesSummary_doesNotUpdateSummary_baseModeRefreshRateNotSet() {
         val summary = createVotesSummary()
 
         baseModeVote.updateSummary(summary)
@@ -48,7 +48,7 @@
     }
 
     @Test
-    fun `keeps summary base mode refresh rate if set`() {
+    fun doesNotUpdateSummary_baseModeRefreshRateSet() {
         val summary = createVotesSummary()
         summary.appRequestBaseModeRefreshRate = OTHER_BASE_REFRESH_RATE
 
@@ -58,7 +58,7 @@
     }
 
     @Test
-    fun `keeps summary with base mode refresh rate if vote refresh rate is negative`() {
+    fun doesNotUpdateSummary_baseModeRefreshRateNotSet_requestedRefreshRateInvalid() {
         val invalidBaseModeVote = BaseModeRefreshRateVote(-10f)
         val summary = createVotesSummary()
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt
index 209e5a3..0a3c285 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/CombinedVoteTest.kt
@@ -45,7 +45,7 @@
     }
 
     @Test
-    fun `delegates update to children`() {
+    fun delegatesUpdateToChildren() {
         val summary = createVotesSummary()
 
         combinedVote.updateSummary(summary)
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt
index 38782c2..5b5ae65 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisableRefreshRateSwitchingVoteTest.kt
@@ -28,7 +28,7 @@
 class DisableRefreshRateSwitchingVoteTest {
 
     @Test
-    fun `disabled refresh rate switching is not changed`(
+    fun testDisableRefreshRateSwitch_alreadyDisabled(
             @TestParameter voteDisableSwitching: Boolean
     ) {
         val summary = createVotesSummary()
@@ -41,7 +41,7 @@
     }
 
     @Test
-    fun `disables refresh rate switching if requested`() {
+    fun disablesRefreshRateSwitch_notDisabled_requested() {
         val summary = createVotesSummary()
         val vote = DisableRefreshRateSwitchingVote(true)
 
@@ -51,7 +51,7 @@
     }
 
     @Test
-    fun `does not disable refresh rate switching if not requested`() {
+    fun doesNotDisableRefreshRateSwitch_notDisabled_notRequested() {
         val summary = createVotesSummary()
         val vote = DisableRefreshRateSwitchingVote(false)
 
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/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt
index 9edcc32..0968edb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/PhysicalVoteTest.kt
@@ -37,7 +37,7 @@
     }
 
     @Test
-    fun `updates minPhysicalRefreshRate if summary has less`() {
+    fun updatesMinPhysicalRefreshRateWithBiggerValue() {
         val summary = createVotesSummary()
         summary.minPhysicalRefreshRate = 45f
 
@@ -47,7 +47,7 @@
     }
 
     @Test
-    fun `does not update minPhysicalRefreshRate if summary has more`() {
+    fun doesNotUpdateMinPhysicalRefreshRateWithSmallerValue() {
         val summary = createVotesSummary()
         summary.minPhysicalRefreshRate = 75f
 
@@ -57,7 +57,7 @@
     }
 
     @Test
-    fun `updates maxPhysicalRefreshRate if summary has more`() {
+    fun updatesMaxPhysicalRefreshRateWithSmallerValue() {
         val summary = createVotesSummary()
         summary.maxPhysicalRefreshRate = 120f
 
@@ -67,7 +67,7 @@
     }
 
     @Test
-    fun `does not update maxPhysicalRefreshRate if summary has less`() {
+    fun doesNotUpdateMaxPhysicalRefreshRateWithBiggerValue() {
         val summary = createVotesSummary()
         summary.maxPhysicalRefreshRate = 75f
 
@@ -77,7 +77,7 @@
     }
 
     @Test
-    fun `updates maxRenderFrameRate if summary has more`() {
+    fun updatesMaxRenderFrameRateWithSmallerValue() {
         val summary = createVotesSummary()
         summary.maxRenderFrameRate = 120f
 
@@ -87,7 +87,7 @@
     }
 
     @Test
-    fun `does not update maxRenderFrameRate if summary has less`() {
+    fun doesNotUpdateMaxRenderFrameRateWithBiggerValue() {
         val summary = createVotesSummary()
         summary.maxRenderFrameRate = 75f
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt
index 2d65f1c..9fa1e1b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/RenderVoteTest.kt
@@ -38,7 +38,7 @@
     }
 
     @Test
-    fun `updates minRenderFrameRate if summary has less`() {
+    fun updatesMinRenderFrameRateWithBiggerValue() {
         val summary = createVotesSummary()
         summary.minRenderFrameRate = 45f
 
@@ -48,7 +48,7 @@
     }
 
     @Test
-    fun `does not update minRenderFrameRate if summary has more`() {
+    fun doesNotUpdateMinRenderFrameRateWithSmallerValue() {
         val summary = createVotesSummary()
         summary.minRenderFrameRate = 75f
 
@@ -58,7 +58,7 @@
     }
 
     @Test
-    fun `updates maxRenderFrameRate if summary has more`() {
+    fun updatesMaxPRenderFrameRateWithSmallerValue() {
         val summary = createVotesSummary()
         summary.maxRenderFrameRate = 120f
 
@@ -68,7 +68,7 @@
     }
 
     @Test
-    fun `does not update maxRenderFrameRate if summary has less`() {
+    fun doesNotUpdateMaxPRenderFrameRateWithBiggerValue() {
         val summary = createVotesSummary()
         summary.maxRenderFrameRate = 75f
 
@@ -78,7 +78,7 @@
     }
 
     @Test
-    fun `updates minPhysicalRefreshRate if summary has less`() {
+    fun updatesMinPhysicalRefreshRateWithBiggerValue() {
         val summary = createVotesSummary()
         summary.minPhysicalRefreshRate = 45f
 
@@ -88,7 +88,7 @@
     }
 
     @Test
-    fun `does not update minPhysicalRefreshRate if summary has more`() {
+    fun doesNotUpdateMinPhysicalRefreshRateWithSmallerValue() {
         val summary = createVotesSummary()
         summary.minPhysicalRefreshRate = 75f
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt
index dbe9e4a..be9c563 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt
@@ -28,7 +28,7 @@
 class RequestedRefreshRateVoteTest {
 
     @Test
-    fun `updates requestedRefreshRates`() {
+    fun testUpdatesRequestedRefreshRates() {
         val refreshRate = 90f
         val vote = RequestedRefreshRateVote(refreshRate)
         val summary = createVotesSummary()
@@ -40,7 +40,7 @@
     }
 
     @Test
-    fun `updates requestedRefreshRates with multiple refresh rates`() {
+    fun testUpdatesRequestedRefreshRates_multipleVotes() {
         val refreshRate1 = 90f
         val vote1 = RequestedRefreshRateVote(refreshRate1)
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index 4fc574a..d7dcca7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -103,7 +103,7 @@
     }
 
     @Test
-    fun `test low power mode`(@TestParameter testCase: LowPowerTestCase) {
+    fun testLowPowerMode(@TestParameter testCase: LowPowerTestCase) {
         whenever(mockFlags.isVsyncLowPowerVoteEnabled).thenReturn(testCase.vsyncLowPowerVoteEnabled)
         whenever(spyContext.contentResolver)
                 .thenReturn(settingsProviderRule.mockContentResolver(null))
@@ -151,7 +151,7 @@
     }
 
     @Test
-    fun `test settings refresh rates`(@TestParameter testCase: SettingsRefreshRateTestCase) {
+    fun testSettingsRefreshRates(@TestParameter testCase: SettingsRefreshRateTestCase) {
         whenever(mockFlags.isPeakRefreshRatePhysicalLimitEnabled)
                 .thenReturn(testCase.peakRefreshRatePhysicalLimitEnabled)
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt
index 1be2fbf..319c21e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SizeVoteTest.kt
@@ -39,7 +39,7 @@
     }
 
     @Test
-    fun `updates size if width and height not set and display resolution voting disabled`() {
+    fun updatesSize_widthAndHeightNotSet_resolutionVotingDisabled() {
         val summary = createVotesSummary(isDisplayResolutionRangeVotingEnabled = false)
         summary.width = Vote.INVALID_SIZE
         summary.height = Vote.INVALID_SIZE
@@ -55,7 +55,7 @@
     }
 
     @Test
-    fun `does not update size if width set and display resolution voting disabled`() {
+    fun doesNotUpdateSiz_widthSet_resolutionVotingDisabled() {
         val summary = createVotesSummary(isDisplayResolutionRangeVotingEnabled = false)
         summary.width = 150
         summary.height = Vote.INVALID_SIZE
@@ -71,7 +71,7 @@
     }
 
     @Test
-    fun `does not update size if height set and display resolution voting disabled`() {
+    fun doesNotUpdateSize_heightSet_resolutionVotingDisabled() {
         val summary = createVotesSummary(isDisplayResolutionRangeVotingEnabled = false)
         summary.width = Vote.INVALID_SIZE
         summary.height = 250
@@ -87,7 +87,7 @@
     }
 
     @Test
-    fun `updates width if summary has more and display resolution voting enabled`() {
+    fun updatesWidthWithSmallerValue_resolutionVotingEnabled() {
         val summary = createVotesSummary()
         summary.width = 850
 
@@ -97,7 +97,7 @@
     }
 
     @Test
-    fun `does not update width if summary has less and display resolution voting enabled`() {
+    fun doesNotUpdateWidthWithBiggerValue_resolutionVotingEnabled() {
         val summary = createVotesSummary()
         summary.width = 750
 
@@ -107,7 +107,7 @@
     }
 
     @Test
-    fun `updates height if summary has more and display resolution voting enabled`() {
+    fun updatesHeightWithSmallerValue_resolutionVotingEnabled() {
         val summary = createVotesSummary()
         summary.height = 1650
 
@@ -117,7 +117,7 @@
     }
 
     @Test
-    fun `does not update height if summary has less and display resolution voting enabled`() {
+    fun doesNotUpdateHeightWithBiggerValue_resolutionVotingEnabled() {
         val summary = createVotesSummary()
         summary.height = 1550
 
@@ -127,7 +127,7 @@
     }
 
     @Test
-    fun `updates minWidth if summary has less and display resolution voting enabled`() {
+    fun updatesMinWidthWithSmallerValue_resolutionVotingEnabled() {
         val summary = createVotesSummary()
         summary.width = 150
         summary.minWidth = 350
@@ -138,7 +138,7 @@
     }
 
     @Test
-    fun `does not update minWidth if summary has more and display resolution voting enabled`() {
+    fun doesNotUpdateMinWidthWithBiggerValue_resolutionVotingEnabled() {
         val summary = createVotesSummary()
         summary.width = 150
         summary.minWidth = 450
@@ -149,7 +149,7 @@
     }
 
     @Test
-    fun `updates minHeight if summary has less and display resolution voting enabled`() {
+    fun updatesMinHeightWithSmallerValue_resolutionVotingEnabled() {
         val summary = createVotesSummary()
         summary.width = 150
         summary.minHeight = 1150
@@ -160,7 +160,7 @@
     }
 
     @Test
-    fun `does not update minHeight if summary has more and display resolution voting enabled`() {
+    fun doesNotUpdateMinHeightWithBiggerValue_resolutionVotingEnabled() {
         val summary = createVotesSummary()
         summary.width = 150
         summary.minHeight = 1250
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
index 6ce49b8..2a50a33 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedModesVoteTest.kt
@@ -39,7 +39,7 @@
     }
 
     @Test
-    fun `adds supported mode ids if supportedModeIds in summary is null`() {
+    fun addsSupportedModeIds_summaryHasNull() {
         val summary = createVotesSummary()
 
         supportedModesVote.updateSummary(summary)
@@ -48,7 +48,7 @@
     }
 
     @Test
-    fun `does not add supported mode ids if summary has empty list of modeIds`() {
+    fun doesNotAddSupportedModeIdes_summaryHasEmptyList() {
         val summary = createVotesSummary()
         summary.supportedModeIds = ArrayList()
 
@@ -58,7 +58,7 @@
     }
 
     @Test
-    fun `filters out modes that does not match vote`() {
+    fun filtersModeIdsThatDoesNotMatchVote() {
         val summary = createVotesSummary()
         summary.supportedModeIds = ArrayList(listOf(otherMode, supportedModes[0]))
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt
index d0c112b..0da6885 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SupportedRefreshRatesVoteTest.kt
@@ -42,7 +42,7 @@
     }
 
     @Test
-    fun `adds supported refresh rates if supportedModes in summary is null`() {
+    fun addsSupportedRefreshRates_summaryHasNull() {
         val summary = createVotesSummary()
 
         supportedRefreshRatesVote.updateSummary(summary)
@@ -51,7 +51,7 @@
     }
 
     @Test
-    fun `does not add supported refresh rates if summary has empty list of refresh rates`() {
+    fun doesNotAddSupportedRefreshRates_summaryHasEmptyList() {
         val summary = createVotesSummary()
         summary.supportedRefreshRates = ArrayList()
 
@@ -61,7 +61,7 @@
     }
 
     @Test
-    fun `filters out supported refresh rates that does not match vote`() {
+    fun filtersSupportedRefreshRatesThatDoesNotMatchVote() {
         val summary = createVotesSummary()
         summary.supportedRefreshRates = ArrayList(listOf(otherMode, refreshRates[0]))
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt
index 5cd3a33..b2d83d7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SyntheticModeManagerTest.kt
@@ -41,7 +41,7 @@
     private val mockConfig = mock<DisplayDeviceConfig>()
 
     @Test
-    fun `test app supported modes`(@TestParameter testCase: AppSupportedModesTestCase) {
+    fun testAppSupportedModes(@TestParameter testCase: AppSupportedModesTestCase) {
         whenever(mockFlags.isSynthetic60HzModesEnabled).thenReturn(testCase.syntheticModesEnabled)
         whenever(mockConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported)
         val syntheticModeManager = SyntheticModeManager(mockFlags)
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
index c49205b..9ea7ea7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SystemRequestObserverTest.kt
@@ -51,7 +51,7 @@
     private val storage = VotesStorage({}, null)
 
     @Test
-    fun `requestDisplayModes adds vote to storage`() {
+    fun testRequestDisplayModes_voteAdded() {
         val systemRequestObserver = SystemRequestObserver(storage)
         val requestedModes = intArrayOf(1, 2, 3)
 
@@ -69,7 +69,7 @@
     }
 
     @Test
-    fun `requestDisplayModes overrides votes in storage`() {
+    fun testRequestDisplayModes_voteReplaced() {
         val systemRequestObserver = SystemRequestObserver(storage)
 
         systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, intArrayOf(1, 2, 3))
@@ -89,7 +89,7 @@
     }
 
     @Test
-    fun `requestDisplayModes removes vote to storage`() {
+    fun testRequestDisplayModes_voteRemoved() {
         val systemRequestObserver = SystemRequestObserver(storage)
         val requestedModes = intArrayOf(1, 2, 3)
 
@@ -101,7 +101,7 @@
     }
 
     @Test
-    fun `requestDisplayModes calls linkToDeath to token`() {
+    fun testTokenLinkToDeath() {
         val systemRequestObserver = SystemRequestObserver(storage)
         val requestedModes = intArrayOf(1, 2, 3)
 
@@ -111,7 +111,7 @@
     }
 
     @Test
-    fun `does not add votes to storage if binder died when requestDisplayModes called`() {
+    fun testBinderDied_voteRemoved() {
         val systemRequestObserver = SystemRequestObserver(storage)
         val requestedModes = intArrayOf(1, 2, 3)
 
@@ -123,7 +123,7 @@
     }
 
     @Test
-    fun `removes all votes from storage when binder dies`() {
+    fun testBinderDied_allVotesRemoved() {
         val systemRequestObserver = SystemRequestObserver(storage)
         val requestedModes = intArrayOf(1, 2, 3)
 
@@ -138,7 +138,7 @@
     }
 
     @Test
-    fun `calls unlinkToDeath on token when no votes remaining`() {
+    fun testTokenUnlinkToDeath_noMoreVotes() {
         val systemRequestObserver = SystemRequestObserver(storage)
         val requestedModes = intArrayOf(1, 2, 3)
 
@@ -149,7 +149,7 @@
     }
 
     @Test
-    fun `does not call unlinkToDeath on token when votes for other display in storage`() {
+    fun testTokenUnlinkToDeathNotCalled_votesForOtherDisplayInStorage() {
         val systemRequestObserver = SystemRequestObserver(storage)
         val requestedModes = intArrayOf(1, 2, 3)
 
@@ -161,7 +161,7 @@
     }
 
     @Test
-    fun `requestDisplayModes subset modes from different tokens`() {
+    fun testRequestDisplayModes_differentToken_voteHasModesSubset() {
         val systemRequestObserver = SystemRequestObserver(storage)
         val requestedModes = intArrayOf(1, 2, 3)
         systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
@@ -187,7 +187,7 @@
     }
 
     @Test
-    fun `recalculates vote if one binder dies`() {
+    fun testBinderDies_recalculatesVotes() {
         val systemRequestObserver = SystemRequestObserver(storage)
         val requestedModes = intArrayOf(1, 2, 3)
         systemRequestObserver.requestDisplayModes(mockToken, DISPLAY_ID, requestedModes)
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
index dd5e1be..239e59b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
@@ -80,7 +80,7 @@
     }
 
     @Test
-    fun `filters modes for summary supportedRefreshRates`(
+    fun testFiltersModes_supportedRefreshRates(
             @TestParameter testCase: SupportedRefreshRatesTestCase
     ) {
         val summary = createSummary(testCase.supportedModesVoteEnabled)
@@ -142,9 +142,7 @@
     }
 
     @Test
-    fun `filters modes for summary supportedModes`(
-            @TestParameter testCase: SupportedModesTestCase
-    ) {
+    fun testFiltersModes_supportedModes(@TestParameter testCase: SupportedModesTestCase) {
         val summary = createSummary(testCase.supportedModesVoteEnabled)
         summary.supportedModeIds = testCase.summarySupportedModes
 
@@ -154,7 +152,7 @@
     }
 
     @Test
-    fun `summary invalid if has requestedRefreshRate less than minRenederRate`() {
+    fun testInvalidSummary_requestedRefreshRateLessThanMinRenderRate() {
         val summary = createSummary()
         summary.requestedRefreshRates = setOf(30f, 90f)
         summary.minRenderFrameRate = 60f
@@ -166,7 +164,7 @@
     }
 
     @Test
-    fun `summary invalid if has requestedRefreshRate more than maxRenderFrameRate`() {
+    fun testInvalidSummary_requestedRefreshRateMoreThanMaxRenderRate() {
         val summary = createSummary()
         summary.requestedRefreshRates = setOf(60f, 240f)
         summary.minRenderFrameRate = 60f
@@ -178,7 +176,7 @@
     }
 
     @Test
-    fun `summary valid if all requestedRefreshRates inside render rate limits`() {
+    fun testValidSummary_requestedRefreshRatesWithingRenderRateLimits() {
         val summary = createSummary()
         summary.requestedRefreshRates = setOf(60f, 90f)
         summary.minRenderFrameRate = 60f
diff --git a/services/tests/dreamservicetests/Android.bp b/services/tests/dreamservicetests/Android.bp
index 6369d45..1f0e975 100644
--- a/services/tests/dreamservicetests/Android.bp
+++ b/services/tests/dreamservicetests/Android.bp
@@ -42,3 +42,13 @@
         enabled: false,
     },
 }
+
+test_module_config {
+    name: "DreamServiceTests_server_dreams",
+    base: "DreamServiceTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.dreams"],
+}
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/Android.bp b/services/tests/mockingservicestests/Android.bp
index 9808d54..5a76931 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -333,3 +333,73 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.utils"],
 }
+
+test_module_config {
+    name: "FrameworksMockingServicesTests_android_server",
+    base: "FrameworksMockingServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server"],
+}
+
+test_module_config {
+    name: "FrameworksMockingServicesTests_server_job",
+    base: "FrameworksMockingServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.job"],
+}
+
+test_module_config {
+    name: "FrameworksMockingServicesTests_server_tare",
+    base: "FrameworksMockingServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.tare"],
+}
+
+test_module_config {
+    name: "FrameworksMockingServicesTests_server_backup",
+    base: "FrameworksMockingServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.backup"],
+}
+
+test_module_config {
+    name: "FrameworksMockingServicesTests_server_rescuepartytest",
+    base: "FrameworksMockingServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.RescuePartyTest"],
+}
+
+test_module_config {
+    name: "FrameworksMockingServicesTests_server_power",
+    base: "FrameworksMockingServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.power"],
+}
+
+test_module_config {
+    name: "FrameworksMockingServicesTests_server_trust",
+    base: "FrameworksMockingServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.trust"],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index e610a32..809e13c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,8 +28,8 @@
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
-import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.os.PowerExemptionManager.REASON_DENIED;
 import static android.os.UserHandle.USER_ALL;
 import static android.util.DebugUtils.valueToString;
 
@@ -68,11 +68,9 @@
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -84,7 +82,6 @@
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
 import android.app.ForegroundServiceDelegationOptions;
-import android.app.IApplicationThread;
 import android.app.IUidObserver;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -129,7 +126,7 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.sdksandbox.flags.Flags;
 import com.android.server.LocalServices;
-import com.android.server.am.ActivityManagerService.StickyBroadcast;
+import com.android.server.am.BroadcastController.StickyBroadcast;
 import com.android.server.am.ProcessList.IsolatedUidRange;
 import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
 import com.android.server.am.UidObserverController.ChangeRecord;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index ee96c2a..3dd2f24a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -22,6 +22,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -42,6 +43,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.text.TextUtils;
 
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.appop.AppOpsService;
@@ -121,11 +124,18 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
 
+        mAppStartInfoTracker.mMonotonicClock = new MonotonicClock(
+                Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
         mAppStartInfoTracker.clearProcessStartInfo(true);
         mAppStartInfoTracker.mAppStartInfoLoaded.set(true);
         mAppStartInfoTracker.mAppStartInfoHistoryListSize =
                 mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE;
         doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean());
+
+        mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
+                AppStartInfoTracker.APP_START_STORE_DIR);
+        mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
+                AppStartInfoTracker.APP_START_INFO_FILE);
     }
 
     @After
@@ -135,11 +145,8 @@
 
     @Test
     public void testApplicationStartInfo() throws Exception {
-        mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
-                AppStartInfoTracker.APP_START_STORE_DIR);
+        // Make sure we can write to the file.
         assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
-        mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
-                AppStartInfoTracker.APP_START_INFO_FILE);
 
         final long appStartTimestampIntentStarted = 1000000;
         final long appStartTimestampActivityLaunchFinished = 2000000;
@@ -482,6 +489,79 @@
         verifyInProgressRecordsSize(AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS);
     }
 
+    /**
+     * Test to make sure that records are returned in correct order, from most recently added at
+     * index 0 to least recently added at index size - 1.
+     */
+    @Test
+    public void testHistoricalRecordsOrdering() throws Exception {
+        // Clear old records
+        mAppStartInfoTracker.clearProcessStartInfo(false);
+
+        // Add some records with timestamps 0 decreasing as clock increases.
+        ProcessRecord app = makeProcessRecord(
+                APP_1_PID_1,                     // pid
+                APP_1_UID,                       // uid
+                APP_1_UID,                       // packageUid
+                null,                            // definingUid
+                APP_1_PROCESS_NAME,              // processName
+                APP_1_PACKAGE_NAME);             // packageName
+
+        mAppStartInfoTracker.handleProcessBroadcastStart(3, app, buildIntent(COMPONENT),
+                false /* isAlarm */);
+        mAppStartInfoTracker.handleProcessBroadcastStart(2, app, buildIntent(COMPONENT),
+                false /* isAlarm */);
+        mAppStartInfoTracker.handleProcessBroadcastStart(1, app, buildIntent(COMPONENT),
+                false /* isAlarm */);
+
+        // Get records
+        ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
+        mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
+
+        // Confirm that records are in correct order, with index 0 representing the most recently
+        // added record and index size - 1 representing the least recently added one.
+        assertEquals(3, list.size());
+        assertEquals(1L, list.get(0).getStartupTimestamps().get(0).longValue());
+        assertEquals(2L, list.get(1).getStartupTimestamps().get(0).longValue());
+        assertEquals(3L, list.get(2).getStartupTimestamps().get(0).longValue());
+    }
+
+    /**
+     * Test to make sure that persist and restore correctly maintains the state of the monotonic
+     * clock.
+     */
+    @Test
+    public void testPersistAndRestoreMonotonicClock() {
+        // Make sure we can write to the file.
+        assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
+
+        // No need to persist records for this test, clear any that may be there.
+        mAppStartInfoTracker.clearProcessStartInfo(false);
+
+        // Set clock with an arbitrary 5 minute offset, just needs to be longer than it would take
+        // for code to run.
+        mAppStartInfoTracker.mMonotonicClock = new MonotonicClock(5 * 60 * 1000,
+                Clock.SYSTEM_CLOCK);
+
+        // Record the current time.
+        long originalMonotonicTime = mAppStartInfoTracker.mMonotonicClock.monotonicTime();
+
+        // Now persist the process start info. Records were cleared above so this should just
+        // persist the monotonic time.
+        mAppStartInfoTracker.persistProcessStartInfo();
+
+        // Null out the clock to make sure its set on load.
+        mAppStartInfoTracker.mMonotonicClock = null;
+        assertNull(mAppStartInfoTracker.mMonotonicClock);
+
+        // Now load from disk.
+        mAppStartInfoTracker.loadExistingProcessStartInfo();
+
+        // Confirm clock has been set and that its current time is greater than the previous one.
+        assertNotNull(mAppStartInfoTracker.mMonotonicClock);
+        assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() > originalMonotonicTime);
+    }
+
     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
         try {
             Field field = clazz.getDeclaredField(fieldName);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 0ba74c6..100b548 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1176,6 +1176,17 @@
         verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
         verifyPendingRecords(redQueue, List.of(screenOff));
         verifyPendingRecords(blueQueue, List.of(screenOff, screenOn));
+
+        final BroadcastRecord screenOffRecord = makeBroadcastRecord(screenOff, screenOnOffOptions,
+                List.of(greenReceiver, redReceiver, blueReceiver), false);
+        screenOffRecord.setDeliveryState(2, BroadcastRecord.DELIVERY_DEFERRED,
+                "testDeliveryGroupPolicy_prioritized_diffReceivers");
+        mImpl.enqueueBroadcastLocked(screenOffRecord);
+        mImpl.enqueueBroadcastLocked(makeBroadcastRecord(screenOn, screenOnOffOptions,
+                List.of(greenReceiver, blueReceiver), false));
+        verifyPendingRecords(greenQueue, List.of(screenOff, screenOn));
+        verifyPendingRecords(redQueue, List.of(screenOff));
+        verifyPendingRecords(blueQueue, List.of(screenOn));
     }
 
     /**
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..3b284a2 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;
@@ -89,7 +90,6 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.ApplicationExitInfo;
-import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.content.ComponentName;
 import android.content.Context;
@@ -163,6 +163,10 @@
 
     private static int sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ
             + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+    private static int sFirstUiCachedAdj = ProcessList.CACHED_APP_MIN_ADJ + 10;
+    private static int sFirstNonUiCachedAdj = ProcessList.CACHED_APP_MIN_ADJ + 20;
+    private static int sUiTierSize = 5;
+
     private Context mContext;
     private PackageManagerInternal mPackageManagerInternal;
     private ActivityManagerService mService;
@@ -231,9 +235,6 @@
                         mInjector);
         mService.mOomAdjuster.mAdjSeq = 10000;
         mService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        if (mService.mConstants.USE_TIERED_CACHED_ADJ) {
-            sFirstCachedAdj = ProcessList.CACHED_APP_MIN_ADJ + 10;
-        }
         mSetFlagsRule.enableFlags(Flags.FLAG_NEW_FGS_RESTRICTION_LOGIC);
     }
 
@@ -472,7 +473,8 @@
         mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(app);
 
-        final int expectedAdj = sFirstCachedAdj;
+        final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                ? sFirstUiCachedAdj : sFirstCachedAdj;
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj,
                 SCHED_GROUP_BACKGROUND);
     }
@@ -700,7 +702,9 @@
             mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
             mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
 
-            assertEquals(sFirstCachedAdj, app.mState.getSetAdj());
+            final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                    ? sFirstUiCachedAdj : sFirstCachedAdj;
+            assertEquals(expectedAdj, app.mState.getSetAdj());
             // Follow up should not have been called again.
             verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
                     followUpTimeCaptor.capture());
@@ -835,7 +839,9 @@
         mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
         mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
 
-        assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, CACHED_APP_MIN_ADJ,
+        int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                ? sFirstUiCachedAdj : CACHED_APP_MIN_ADJ;
+        assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
                 SCHED_GROUP_BACKGROUND, "previous-expired");
         // Follow up should not have been called again.
         verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
@@ -844,6 +850,55 @@
 
     @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;
+            if (mService.mConstants.USE_TIERED_CACHED_ADJ) {
+                expectedAdj = (i < numberOfApps - sUiTierSize)
+                        ? sFirstNonUiCachedAdj : sFirstUiCachedAdj + mruIndex;
+            } else {
+                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));
@@ -959,7 +1014,9 @@
         updateOomAdj(client, app);
         doReturn(null).when(mService).getTopApp();
 
-        assertProcStates(app, PROCESS_STATE_SERVICE, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
+        final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                ? sFirstUiCachedAdj : sFirstCachedAdj;
+        assertProcStates(app, PROCESS_STATE_SERVICE, expectedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1009,7 +1066,9 @@
         mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(app);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
+        final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1425,7 +1484,9 @@
         bindProvider(app, app, null, null, false);
         updateOomAdj(app);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
+        final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1440,7 +1501,9 @@
         mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj(app, client);
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND);
+        final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND);
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1548,7 +1611,9 @@
         mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
         mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
 
-        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND,
+        final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+        assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND,
                 "cch-empty");
         // Follow up should not have been called again.
         verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
@@ -2579,12 +2644,11 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         final int userOwner = 0;
         final int userOther = 1;
-        final int cachedAdj1 = mService.mConstants.USE_TIERED_CACHED_ADJ
-                ? CACHED_APP_MIN_ADJ + 10
-                : CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
-        final int cachedAdj2 = mService.mConstants.USE_TIERED_CACHED_ADJ
-                ? CACHED_APP_MIN_ADJ + 10
-                : cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+
+        // cachedAdj1 and cachedAdj2 will be read if USE_TIERED_CACHED_ADJ is disabled. Otherwise,
+        // sFirstUiCachedAdj and sFirstNonUiCachedAdj are used instead.
+        final int cachedAdj1 = CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+        final int cachedAdj2 = cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
         doReturn(userOwner).when(mService.mUserController).getCurrentUserId();
 
         final ArrayList<ProcessRecord> lru = mService.mProcessList.getLruProcessesLOSP();
@@ -2625,8 +2689,12 @@
         mService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         updateOomAdj();
 
-        assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
-        assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
+        assertProcStates(app, true, PROCESS_STATE_SERVICE,
+                mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstUiCachedAdj : cachedAdj1,
+                "cch-started-ui-services");
+        assertProcStates(app2, true, PROCESS_STATE_SERVICE,
+                mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj2,
+                "cch-started-services");
 
         app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
         app.mState.setAdjType(null);
@@ -2642,7 +2710,10 @@
         s.lastActivity = now - mService.mConstants.MAX_SERVICE_INACTIVITY - 1;
         updateOomAdj();
 
-        assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
+        // hasShownUi was set to false for 'app', so 920 is expected for USE_TIERED_CACHED_ADJ.
+        assertProcStates(app, true, PROCESS_STATE_SERVICE,
+                mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj1,
+                "cch-started-services");
 
         app.mServices.stopService(s);
         app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
@@ -2661,7 +2732,9 @@
         updateOomAdj();
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
-        assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
+        assertProcStates(app2, true, PROCESS_STATE_SERVICE,
+                mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj1,
+                "cch-started-services");
 
         app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
         app.mState.setAdjType(null);
@@ -2671,13 +2744,17 @@
         updateOomAdj();
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
-        assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
+        assertProcStates(app2, true, PROCESS_STATE_SERVICE,
+                mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj1,
+                "cch-started-services");
 
         doReturn(userOther).when(mService.mUserController).getCurrentUserId();
         mService.mOomAdjuster.handleUserSwitchedLocked();
 
         updateOomAdj();
-        assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
+        assertProcStates(app, true, PROCESS_STATE_SERVICE,
+                mService.mConstants.USE_TIERED_CACHED_ADJ ? sFirstNonUiCachedAdj : cachedAdj1,
+                "cch-started-services");
         assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
     }
 
@@ -2954,7 +3031,9 @@
         mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
         mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
 
-        assertProcStates(app, PROCESS_STATE_SERVICE, sFirstCachedAdj, SCHED_GROUP_BACKGROUND,
+        final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+        assertProcStates(app, PROCESS_STATE_SERVICE, expectedAdj, SCHED_GROUP_BACKGROUND,
                 "cch-started-services");
         // Follow up should not have been called again.
         verify(mService.mHandler).sendEmptyMessageAtTime(eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG),
@@ -2987,14 +3066,16 @@
         mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
         mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
 
-        assertProcStates(app1, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND,
+        final int expectedAdj = mService.mConstants.USE_TIERED_CACHED_ADJ
+                ? sFirstNonUiCachedAdj : sFirstCachedAdj;
+        assertProcStates(app1, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND,
                 "cch-empty");
 
         verify(mService.mHandler, atLeastOnce()).sendEmptyMessageAtTime(
                 eq(FOLLOW_UP_OOMADJUSTER_UPDATE_MSG), followUpTimeCaptor.capture());
         mInjector.jumpUptimeAheadTo(followUpTimeCaptor.getValue());
         mService.mOomAdjuster.updateOomAdjFollowUpTargetsLocked();
-        assertProcStates(app2, PROCESS_STATE_CACHED_EMPTY, sFirstCachedAdj, SCHED_GROUP_BACKGROUND,
+        assertProcStates(app2, PROCESS_STATE_CACHED_EMPTY, expectedAdj, SCHED_GROUP_BACKGROUND,
                 "cch-empty");
     }
 
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/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryUtilsTest.java
new file mode 100644
index 0000000..6f38fca
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/CrashRecoveryUtilsTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.crashrecovery;
+
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.quality.Strictness.LENIENT;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.Environment;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+
+/**
+ * Test CrashRecovery Utils.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CrashRecoveryUtilsTest {
+
+    private MockitoSession mStaticMockSession;
+    private final String mLogMsg = "Logging from test";
+    private final String mCrashrecoveryEventTag = "CrashRecovery Events: ";
+    private File mCacheDir;
+
+    @Before
+    public void setup() throws IOException {
+        Context context = ApplicationProvider.getApplicationContext();
+        mCacheDir = context.getCacheDir();
+        mStaticMockSession = ExtendedMockito.mockitoSession()
+                .spyStatic(Environment.class)
+                .strictness(LENIENT)
+                .startMocking();
+        ExtendedMockito.doReturn(mCacheDir).when(() -> Environment.getDataDirectory());
+
+        createCrashRecoveryEventsTempDir();
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        mStaticMockSession.finishMocking();
+        deleteCrashRecoveryEventsTempFile();
+    }
+
+    @Test
+    public void testCrashRecoveryUtils() {
+        testLogCrashRecoveryEvent();
+        testDumpCrashRecoveryEvents();
+    }
+
+    @Test
+    public void testDumpCrashRecoveryEventsWithoutAnyLogs() {
+        assertThat(getCrashRecoveryEventsTempFile().exists()).isFalse();
+        StringWriter sw = new StringWriter();
+        IndentingPrintWriter ipw = new IndentingPrintWriter(sw, "  ");
+        CrashRecoveryUtils.dumpCrashRecoveryEvents(ipw);
+        ipw.close();
+
+        String dump = sw.getBuffer().toString();
+        assertThat(dump).contains(mCrashrecoveryEventTag);
+        assertThat(dump).doesNotContain(mLogMsg);
+    }
+
+    private void testLogCrashRecoveryEvent() {
+        assertThat(getCrashRecoveryEventsTempFile().exists()).isFalse();
+        CrashRecoveryUtils.logCrashRecoveryEvent(Log.WARN, mLogMsg);
+
+        assertThat(getCrashRecoveryEventsTempFile().exists()).isTrue();
+        String fileContent = null;
+        try {
+            File file = getCrashRecoveryEventsTempFile();
+            FileInputStream fis = new FileInputStream(file);
+            byte[] data = new byte[(int) file.length()];
+            fis.read(data);
+            fis.close();
+            fileContent = new String(data, StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            fail("Unable to read the events file");
+        }
+        assertThat(fileContent).contains(mLogMsg);
+    }
+
+    private void testDumpCrashRecoveryEvents() {
+        StringWriter sw = new StringWriter();
+        IndentingPrintWriter ipw = new IndentingPrintWriter(sw, "  ");
+        CrashRecoveryUtils.dumpCrashRecoveryEvents(ipw);
+        ipw.close();
+
+        String dump = sw.getBuffer().toString();
+        assertThat(dump).contains(mCrashrecoveryEventTag);
+        assertThat(dump).contains(mLogMsg);
+    }
+
+    private void createCrashRecoveryEventsTempDir() throws IOException {
+        Files.deleteIfExists(getCrashRecoveryEventsTempFile().toPath());
+        File mMockDirectory = new File(mCacheDir, "system");
+        if (!mMockDirectory.exists()) {
+            assertThat(mMockDirectory.mkdir()).isTrue();
+        }
+    }
+
+    private void deleteCrashRecoveryEventsTempFile() throws IOException {
+        Files.deleteIfExists(getCrashRecoveryEventsTempFile().toPath());
+    }
+
+    private File getCrashRecoveryEventsTempFile() {
+        File systemTempDir = new File(mCacheDir, "system");
+        return new File(systemTempDir, "crashrecovery-events.txt");
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 37d87c4e..1cba3c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -52,6 +52,7 @@
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.multiuser.Flags;
 import android.os.PowerManager;
 import android.os.ServiceSpecificException;
@@ -152,6 +153,7 @@
     private File mTestDir;
 
     private Context mSpiedContext;
+    private Resources mSpyResources;
 
     private @Mock PackageManagerService mMockPms;
     private @Mock UserDataPreparer mMockUserDataPreparer;
@@ -193,6 +195,13 @@
         doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
         mockIsLowRamDevice(false);
 
+        // Called when getting boot user. config_bootToHeadlessSystemUser is false by default.
+        mSpyResources = spy(mSpiedContext.getResources());
+        when(mSpiedContext.getResources()).thenReturn(mSpyResources);
+        doReturn(false)
+                .when(mSpyResources)
+                .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+
         // Must construct UserManagerService in the UiThread
         mTestDir = new File(mRealContext.getDataDir(), "umstest");
         mTestDir.mkdirs();
@@ -849,6 +858,16 @@
                         USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
     }
 
+    @Test
+    public void testGetBootUser_enableBootToHeadlessSystemUser() {
+        setSystemUserHeadless(true);
+        doReturn(true)
+                .when(mSpyResources)
+                .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+
+        assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
+    }
+
     /**
      * Returns true if the user's XML file has Default restrictions
      * @param userId Id of the user.
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
index e94b8ad..6a16d1e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -54,3 +54,13 @@
         "automotive-tests",
     ],
 }
+
+test_module_config {
+    name: "RollbackPackageHealthObserverTests_server_rollback",
+    base: "RollbackPackageHealthObserverTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.rollback"],
+}
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 7d04470..b2ca991 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -631,7 +631,7 @@
         CountDownLatch stopLatch2 = new CountDownLatch(1);
         // negative value used for test only to avoid conflicting with any real thread that exists
         int isoProc1 = -100;
-        int isoProc2 = 9999;
+        int isoProc2 = 99999999;
         when(mAmInternalMock.getIsolatedProcesses(eq(UID))).thenReturn(List.of(0));
         int[] tids2 = createThreads(threadCount, stopLatch2);
         int[] tids2WithIsolated = Arrays.copyOf(tids2, tids2.length + 2);
@@ -658,7 +658,7 @@
         verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any());
         verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any());
         // the new TIDs pending list should be updated
-        assertArrayEquals(session2.getTidsInternal(), expectedTids2);
+        assertArrayEquals(expectedTids2, session2.getTidsInternal());
         reset(mNativeWrapperMock);
 
         // this should resume and update the threads so those never-existed invalid isolated
@@ -713,8 +713,8 @@
         // in background, set threads for session 1 then it should not be force paused next time
         session1.setThreads(SESSION_TIDS_A);
         // the new TIDs pending list should be updated
-        assertArrayEquals(session1.getTidsInternal(), SESSION_TIDS_A);
-        assertArrayEquals(session2.getTidsInternal(), expectedTids2);
+        assertArrayEquals(SESSION_TIDS_A, session1.getTidsInternal());
+        assertArrayEquals(expectedTids2, session2.getTidsInternal());
         verifyAllHintsEnabled(session1, false);
         verifyAllHintsEnabled(session2, false);
         reset(mNativeWrapperMock);
diff --git a/services/tests/powerservicetests/Android.bp b/services/tests/powerservicetests/Android.bp
index 729dcbd..f8cc6d0 100644
--- a/services/tests/powerservicetests/Android.bp
+++ b/services/tests/powerservicetests/Android.bp
@@ -44,3 +44,13 @@
         enabled: false,
     },
 }
+
+test_module_config {
+    name: "PowerServiceTests_server_power",
+    base: "PowerServiceTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.power"],
+}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
index 1c4db6a..c1d7c7b 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.PowerManager;
+import android.os.Process;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -54,6 +55,8 @@
 
         when(mPackageManager.getPackagesForUid(101)).thenReturn(new String[]{ "some.package1" });
         when(mPackageManager.getPackagesForUid(102)).thenReturn(new String[]{ "some.package2" });
+        when(mPackageManager.getPackagesForUid(Process.SYSTEM_UID))
+                .thenReturn(new String[]{ "some.package3" });
     }
 
     @Test
@@ -70,14 +73,20 @@
         log.onWakeLockAcquired("TagFull", 102,
                 PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, -1);
 
+        when(injectorSpy.currentTimeMillis()).thenReturn(1250L);
+        log.onWakeLockAcquired("TagSystem", 1000,
+                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, -1);
+
         assertEquals("Wake Lock Log\n"
                         + "  01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial "
                         + "(partial,on-after-release)\n"
                         + "  01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull "
                         + "(full,acq-causes-wake)\n"
+                        + "  01-01 00:00:01.250 - 1000 (" + WakeLockLog.SYSTEM_PACKAGE_NAME + ")"
+                        + " - ACQ TagSystem (full,acq-causes-wake)\n"
                         + "  -\n"
-                        + "  Events: 2, Time-Resets: 0\n"
-                        + "  Buffer, Bytes used: 6\n",
+                        + "  Events: 3, Time-Resets: 0\n"
+                        + "  Buffer, Bytes used: 9\n",
                 dumpLog(log, false));
     }
 
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index b2a5b02..0e922ce 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -71,9 +71,31 @@
     ],
     srcs: [
         "src/com/android/server/power/stats/*.java",
+        "src/com/android/server/power/stats/format/*.java",
+        "src/com/android/server/power/stats/processor/*.java",
     ],
     java_resources: [
         "res/xml/power_profile*.xml",
     ],
     auto_gen_config: true,
 }
+
+test_module_config {
+    name: "PowerStatsTests_stats_bstatscputimesvalidationtest",
+    base: "PowerStatsTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.power.stats.BstatsCpuTimesValidationTest"],
+}
+
+test_module_config {
+    name: "PowerStatsTests_power_stats",
+    base: "PowerStatsTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.power.stats"],
+}
diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING
index fb24361..1e8d2de 100644
--- a/services/tests/powerstatstests/TEST_MAPPING
+++ b/services/tests/powerstatstests/TEST_MAPPING
@@ -14,8 +14,7 @@
       "name": "PowerStatsTestsRavenwood",
       "host": true,
       "options": [
-        {"include-filter": "com.android.server.power.stats"},
-        {"exclude-annotation": "android.platform.test.annotations.DisabledOnRavenwood"}
+        {"include-filter": "com.android.server.power.stats"}
       ]
     }
   ],
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index bbab0ee..7b635d4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -38,6 +38,7 @@
 import android.hardware.power.stats.StateResidencyResult;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.connectivity.WifiActivityEnergyInfo;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.power.PowerStatsInternal;
 import android.util.IntArray;
@@ -88,6 +89,33 @@
     }
 
     @Test
+    public void testUpdateWifiState() {
+        WifiActivityEnergyInfo firstInfo = new WifiActivityEnergyInfo(1111,
+                WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 11, 22, 33, 44);
+
+        WifiActivityEnergyInfo delta = mBatteryExternalStatsWorker.extractDeltaLocked(firstInfo);
+
+        assertEquals(1111, delta.getTimeSinceBootMillis());
+        assertEquals(WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, delta.getStackState());
+        assertEquals(0, delta.getControllerTxDurationMillis());
+        assertEquals(0, delta.getControllerRxDurationMillis());
+        assertEquals(0, delta.getControllerScanDurationMillis());
+        assertEquals(0, delta.getControllerIdleDurationMillis());
+
+        WifiActivityEnergyInfo secondInfo = new WifiActivityEnergyInfo(91111,
+                WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, 811, 722, 633, 544);
+
+        delta = mBatteryExternalStatsWorker.extractDeltaLocked(secondInfo);
+
+        assertEquals(91111, delta.getTimeSinceBootMillis());
+        assertEquals(WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, delta.getStackState());
+        assertEquals(800, delta.getControllerTxDurationMillis());
+        assertEquals(700, delta.getControllerRxDurationMillis());
+        assertEquals(600, delta.getControllerScanDurationMillis());
+        assertEquals(500, delta.getControllerIdleDurationMillis());
+    }
+
+    @Test
     public void testTargetedEnergyConsumerQuerying() {
         final int numCpuClusters = 4;
         final int numDisplays = 5;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index a1101cd..1d20538 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -117,7 +117,7 @@
     private PowerStatsStore mPowerStatsStore;
     private BatteryUsageStatsProvider mBatteryUsageStatsProvider;
     @Mock
-    private PowerStatsExporter mPowerStatsExporter;
+    private PowerAttributor mPowerAttributor;
 
     @Before
     public void setUp() throws IOException {
@@ -149,9 +149,8 @@
         } else {
             context = InstrumentationRegistry.getContext();
         }
-        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler,
-                new AggregatedPowerStatsConfig());
-        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerStatsExporter,
+        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler);
+        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mPowerAttributor,
                 mPowerProfile, mBatteryStatsImpl.getCpuScalingPolicies(), mPowerStatsStore,
                 mMockClock);
     }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 17c7efa..fde84e9 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -94,8 +94,9 @@
     public void test_getBatteryUsageStats() {
         BatteryStatsImpl batteryStats = prepareBatteryStats();
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
-                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+                mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
@@ -130,8 +131,9 @@
     public void test_selectPowerComponents() {
         BatteryStatsImpl batteryStats = prepareBatteryStats();
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
-                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+                mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats,
@@ -235,8 +237,9 @@
             batteryStats.noteAlarmFinishLocked("foo", null, APP_UID, 3_001_000, 2_001_000);
         }
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
-                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+                mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats,
@@ -323,8 +326,9 @@
             }
         }
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
-                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+                mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
 
         final BatteryUsageStats batteryUsageStats =
                 provider.getBatteryUsageStats(batteryStats,
@@ -408,12 +412,12 @@
 
         PowerStatsStore powerStatsStore = new PowerStatsStore(
                 new File(mStatsRule.getHistoryDir(), "powerstatsstore"),
-                mStatsRule.getHandler(), null);
+                mStatsRule.getHandler());
         powerStatsStore.reset();
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
-                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore,
-                mMockClock);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+                mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
+                mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock);
 
         batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore);
         synchronized (batteryStats) {
@@ -522,8 +526,9 @@
             batteryStats.updateCustomEnergyConsumerStatsLocked(1, 200_000_000, uidEnergies);
         }
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
-                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), null, mMockClock);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+                mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
+                mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), mMockClock);
 
         PowerStatsStore powerStatsStore = mock(PowerStatsStore.class);
         doAnswer(invocation -> {
@@ -584,9 +589,9 @@
         when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE))
                 .thenReturn(span1);
 
-        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, null,
-                mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore,
-                mMockClock);
+        BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
+                mock(PowerAttributor.class), mStatsRule.getPowerProfile(),
+                mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock);
 
         BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
                 .aggregateSnapshots(0, 3000)
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsCollectorTest.java
index 02c7b74..e392c5d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsCollectorTest.java
@@ -27,6 +27,7 @@
 import android.bluetooth.UidTraffic;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 import android.os.Handler;
@@ -37,6 +38,7 @@
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.BluetoothPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -48,7 +50,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
-import java.util.function.IntSupplier;
 
 public class BluetoothPowerStatsCollectorTest {
     private static final int APP_UID1 = 42;
@@ -132,11 +133,6 @@
                 }
 
                 @Override
-                public IntSupplier getVoltageSupplier() {
-                    return () -> 3500;
-                }
-
-                @Override
                 public BluetoothPowerStatsCollector.BluetoothStatsRetriever
                         getBluetoothStatsRetriever() {
                     return mBluetoothStatsRetriever;
@@ -232,6 +228,7 @@
         BluetoothPowerStatsCollector collector = new BluetoothPowerStatsCollector(mInjector);
         collector.setEnabled(true);
 
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(3500);
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH))
                 .thenReturn(new int[]{777});
 
@@ -242,8 +239,7 @@
 
         mUidScanTimes.put(APP_UID1, 100);
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777})))
-                .thenReturn(new long[]{10000});
+        mockConsumedEnergy(777, 10000);
 
         // Establish a baseline
         collector.collectStats();
@@ -258,13 +254,19 @@
         mUidScanTimes.put(APP_UID2, 300);
         mUidScanTimes.put(ISOLATED_UID, 400);
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777})))
-                .thenReturn(new long[]{64321});
+        mockConsumedEnergy(777, 64321);
 
         mStatsRule.setTime(20000, 20000);
         return collector.collectStats();
     }
 
+    private void mockConsumedEnergy(int consumerId, long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        when(mConsumedEnergyRetriever.getConsumedEnergy(eq(new int[]{consumerId})))
+                .thenReturn(new EnergyConsumerResult[]{ecr});
+    }
+
     private BluetoothActivityEnergyInfo mockBluetoothActivityEnergyInfo(long timestamp,
             long rxTimeMs, long txTimeMs, long idleTimeMs, UidTraffic... uidTraffic) {
         if (RavenwoodRule.isOnRavenwood()) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index d1105a4..1fea462 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.when;
 
+import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 import android.os.ConditionVariable;
@@ -42,6 +43,7 @@
 import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.CpuPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -53,7 +55,6 @@
 
 import java.io.IOException;
 import java.io.StringWriter;
-import java.util.function.IntSupplier;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -124,11 +125,6 @@
         }
 
         @Override
-        public IntSupplier getVoltageSupplier() {
-            return () -> 3500;
-        }
-
-        @Override
         public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
             return 0;
         }
@@ -150,7 +146,9 @@
         mHandlerThread.start();
         mHandler = mHandlerThread.getThreadHandler();
         when(mMockKernelCpuStatsReader.isSupportedFeature()).thenReturn(true);
-        when(mConsumedEnergyRetriever.getEnergyConsumerIds(anyInt())).thenReturn(new int[0]);
+        when(mConsumedEnergyRetriever.getEnergyConsumerIds(anyInt()))
+                .thenReturn(new int[0]);
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(3500);
         mUidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID_2);
     }
 
@@ -228,9 +226,7 @@
         assertThat(descriptor.name).isEqualTo("cpu");
         assertThat(descriptor.statsArrayLength).isEqualTo(13);
         assertThat(descriptor.uidStatsArrayLength).isEqualTo(5);
-        CpuPowerStatsLayout layout =
-                new CpuPowerStatsLayout();
-        layout.fromExtras(descriptor.extras);
+        CpuPowerStatsLayout layout = new CpuPowerStatsLayout(descriptor);
 
         long[] deviceStats = new long[descriptor.statsArrayLength];
         layout.setTimeByScalingStep(deviceStats, 2, 42);
@@ -267,8 +263,7 @@
         mockEnergyConsumers();
 
         CpuPowerStatsCollector collector = createCollector(8, 0);
-        CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
-        layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+        CpuPowerStatsLayout layout = new CpuPowerStatsLayout(collector.getPowerStatsDescriptor());
 
         mockKernelCpuStats(new long[]{1111, 2222, 3333},
                 new SparseArray<>() {{
@@ -338,8 +333,7 @@
         mockEnergyConsumers();
 
         CpuPowerStatsCollector collector = createCollector(8, 0);
-        CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
-        layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+        CpuPowerStatsLayout layout = new CpuPowerStatsLayout(collector.getPowerStatsDescriptor());
 
         mockKernelCpuStats(new long[]{1111, 2222, 3333},
                 new SparseArray<>() {{
@@ -462,17 +456,24 @@
 
     private void mockEnergyConsumers() {
         reset(mConsumedEnergyRetriever);
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(3500);
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.CPU_CLUSTER))
                 .thenReturn(new int[]{1, 2});
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{1, 2})))
-                .thenReturn(new long[]{1000, 2000})
-                .thenReturn(new long[]{1500, 2700});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(eq(new int[]{1, 2})))
+                .thenReturn(new EnergyConsumerResult[]{
+                        mockEnergyConsumer(1000), mockEnergyConsumer(2000)})
+                .thenReturn(new EnergyConsumerResult[]{
+                        mockEnergyConsumer(1500), mockEnergyConsumer(2700)});
+    }
+
+    private EnergyConsumerResult mockEnergyConsumer(long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        return ecr;
     }
 
     private static int[] getScalingStepToPowerBracketMap(CpuPowerStatsCollector collector) {
-        CpuPowerStatsLayout layout =
-                new CpuPowerStatsLayout();
-        layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+        CpuPowerStatsLayout layout = new CpuPowerStatsLayout(collector.getPowerStatsDescriptor());
         return layout.getScalingStepToPowerBracketMap();
     }
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
index ef20946..00b911b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
@@ -33,6 +33,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -51,6 +52,7 @@
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -63,7 +65,6 @@
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.IntSupplier;
 import java.util.function.LongSupplier;
 import java.util.function.Supplier;
 
@@ -139,11 +140,6 @@
         }
 
         @Override
-        public IntSupplier getVoltageSupplier() {
-            return () -> 3500;
-        }
-
-        @Override
         public Supplier<NetworkStats> getMobileNetworkStatsSupplier() {
             return mNetworkStatsSupplier;
         }
@@ -178,6 +174,7 @@
                 return uid;
             }
         });
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(3500);
         mBatteryStats = mStatsRule.getBatteryStats();
     }
 
@@ -242,8 +239,7 @@
         assertThat(powerStats.durationMs).isEqualTo(100);
 
         PowerStats.Descriptor descriptor = powerStats.descriptor;
-        MobileRadioPowerStatsLayout layout =
-                new MobileRadioPowerStatsLayout(descriptor);
+        MobileRadioPowerStatsLayout layout = new MobileRadioPowerStatsLayout(descriptor);
         assertThat(layout.getDeviceSleepTime(powerStats.stats)).isEqualTo(200);
         assertThat(layout.getDeviceIdleTime(powerStats.stats)).isEqualTo(300);
         assertThat(layout.getDeviceCallTime(powerStats.stats)).isEqualTo(40000);
@@ -252,7 +248,7 @@
                 .isEqualTo((64321 - 10000) * 1000 / 3500);
 
         assertThat(powerStats.stateStats.size()).isEqualTo(2);
-        long[] state1 = powerStats.stateStats.get(MobileRadioPowerStatsCollector.makeStateKey(
+        long[] state1 = powerStats.stateStats.get(MobileRadioPowerStatsLayout.makeStateKey(
                 BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR,
                 ServiceState.FREQUENCY_RANGE_MMWAVE
         ));
@@ -263,7 +259,7 @@
         assertThat(layout.getStateTxTime(state1, 3)).isEqualTo(4000);
         assertThat(layout.getStateTxTime(state1, 4)).isEqualTo(5000);
 
-        long[] state2 = powerStats.stateStats.get(MobileRadioPowerStatsCollector.makeStateKey(
+        long[] state2 = powerStats.stateStats.get(MobileRadioPowerStatsLayout.makeStateKey(
                 BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE,
                 ServiceState.FREQUENCY_RANGE_LOW
         ));
@@ -298,15 +294,14 @@
         assertThat(powerStats.durationMs).isEqualTo(100);
 
         PowerStats.Descriptor descriptor = powerStats.descriptor;
-        MobileRadioPowerStatsLayout layout =
-                new MobileRadioPowerStatsLayout(descriptor);
+        MobileRadioPowerStatsLayout layout = new MobileRadioPowerStatsLayout(descriptor);
         assertThat(layout.getDeviceSleepTime(powerStats.stats)).isEqualTo(200);
         assertThat(layout.getDeviceIdleTime(powerStats.stats)).isEqualTo(300);
         assertThat(layout.getConsumedEnergy(powerStats.stats, 0))
                 .isEqualTo((64321 - 10000) * 1000 / 3500);
 
         assertThat(powerStats.stateStats.size()).isEqualTo(1);
-        long[] stateStats = powerStats.stateStats.get(MobileRadioPowerStatsCollector.makeStateKey(
+        long[] stateStats = powerStats.stateStats.get(MobileRadioPowerStatsLayout.makeStateKey(
                 AccessNetworkConstants.AccessNetworkType.UNKNOWN,
                 ServiceState.FREQUENCY_RANGE_UNKNOWN
         ));
@@ -416,8 +411,8 @@
                 4321, 321, 1234, 23,
                 4000, 40, 2000, 20);
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777})))
-                .thenReturn(new long[]{10000});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(eq(new int[]{777})))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(10000)});
 
         when(mCallDurationSupplier.getAsLong()).thenReturn(10000L);
         when(mScanDurationSupplier.getAsLong()).thenReturn(20000L);
@@ -439,8 +434,8 @@
                 5321, 421, 3234, 223,
                 8000, 80, 4000, 40);
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777})))
-                .thenReturn(new long[]{64321});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(eq(new int[]{777})))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(64321)});
         when(mCallDurationSupplier.getAsLong()).thenReturn(50000L);
         when(mScanDurationSupplier.getAsLong()).thenReturn(80000L);
 
@@ -448,6 +443,12 @@
         return collector.collectStats();
     }
 
+    private EnergyConsumerResult mockEnergyConsumer(long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        return ecr;
+    }
+
     private void mockModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs,
             int networkType1, int freqRange1, int rxTimeMs1, @NonNull int[] txTimeMs1,
             int networkType2, int freqRange2, int rxTimeMs2, @NonNull int[] txTimeMs2) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index 89d6c1c..a04f721 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -114,13 +114,15 @@
         mockEnergyConsumers(powerStatsInternal);
 
         PowerStatsCollector.ConsumedEnergyRetrieverImpl retriever =
-                new PowerStatsCollector.ConsumedEnergyRetrieverImpl(powerStatsInternal);
+                new PowerStatsCollector.ConsumedEnergyRetrieverImpl(powerStatsInternal, ()-> 3500);
         int[] energyConsumerIds = retriever.getEnergyConsumerIds(EnergyConsumerType.CPU_CLUSTER);
         assertThat(energyConsumerIds).isEqualTo(new int[]{1, 2});
-        long[] energy = retriever.getConsumedEnergyUws(energyConsumerIds);
-        assertThat(energy).isEqualTo(new long[]{1000, 2000});
-        energy = retriever.getConsumedEnergyUws(energyConsumerIds);
-        assertThat(energy).isEqualTo(new long[]{1500, 2700});
+        EnergyConsumerResult[] energy = retriever.getConsumedEnergy(energyConsumerIds);
+        assertThat(energy[0].energyUWs).isEqualTo(1000);
+        assertThat(energy[1].energyUWs).isEqualTo(2000);
+        energy = retriever.getConsumedEnergy(energyConsumerIds);
+        assertThat(energy[0].energyUWs).isEqualTo(1500);
+        assertThat(energy[1].energyUWs).isEqualTo(2700);
     }
 
     @SuppressWarnings("unchecked")
@@ -176,4 +178,11 @@
                 .thenReturn(future1)
                 .thenReturn(future2);
     }
+
+    private EnergyConsumerResult mockEnergyConsumerResult(long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        return ecr;
+    }
+
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
index beec661..143d046 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
@@ -18,36 +18,18 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.MonotonicClock;
-
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.IOException;
-import java.nio.file.Files;
 import java.time.Duration;
 import java.time.Instant;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
 public class PowerStatsSchedulerTest {
@@ -56,134 +38,10 @@
             .setProvideMainThread(true)
             .build();
 
-    private PowerStatsStore mPowerStatsStore;
-    private Handler mHandler;
-    private MockClock mClock = new MockClock();
-    private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
-    private PowerStatsScheduler mPowerStatsScheduler;
-    private PowerStatsAggregator mPowerStatsAggregator;
-    private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
-    private List<Long> mScheduledAlarms = new ArrayList<>();
-    private boolean mPowerStatsCollectionOccurred;
-
-    private static final int START_REALTIME = 7654321;
-
-    @Before
-    @SuppressWarnings("GuardedBy")
-    public void setup() throws IOException {
-        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
-
-        mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
-        mClock.realtime = START_REALTIME;
-
-        HandlerThread bgThread = new HandlerThread("bg thread");
-        bgThread.start();
-        mHandler = new Handler(bgThread.getLooper());
-        mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
-        mPowerStatsStore = new PowerStatsStore(
-                Files.createTempDirectory("PowerStatsSchedulerTest").toFile(),
-                mHandler, mAggregatedPowerStatsConfig);
-        mPowerStatsAggregator = mock(PowerStatsAggregator.class);
-        mPowerStatsScheduler = new PowerStatsScheduler(
-                () -> mPowerStatsCollectionOccurred = true,
-                mPowerStatsAggregator, TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1),
-                mPowerStatsStore,
-                ((triggerAtMillis, tag, onAlarmListener, handler) ->
-                        mScheduledAlarms.add(triggerAtMillis)),
-                mClock, mMonotonicClock, () -> 12345L, mHandler);
-    }
-
-    @Test
-    @SuppressWarnings("unchecked")
-    public void storeAggregatePowerStats() {
-        mPowerStatsStore.reset();
-
-        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
-
-        mPowerStatsStore.storeAggregatedPowerStats(
-                createAggregatedPowerStats(mMonotonicClock.monotonicTime(), mClock.currentTime,
-                        123));
-
-        long delayBeforeAggregating = TimeUnit.MINUTES.toMillis(90);
-        mClock.realtime += delayBeforeAggregating;
-        mClock.currentTime += delayBeforeAggregating;
-
-        doAnswer(invocation -> {
-            // The first span is longer than 30 min, because the end time is being aligned with
-            // the wall clock.  Subsequent spans should be precisely 30 minutes.
-            long startTime = invocation.getArgument(0);
-            long endTime = invocation.getArgument(1);
-            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
-
-            long startTimeWallClock =
-                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
-            long endTimeWallClock =
-                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
-
-            assertThat(startTime).isEqualTo(START_REALTIME + 123);
-            assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30));
-            assertThat(Instant.ofEpochMilli(endTimeWallClock))
-                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
-
-            consumer.accept(
-                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
-            return null;
-        }).doAnswer(invocation -> {
-            long startTime = invocation.getArgument(0);
-            long endTime = invocation.getArgument(1);
-            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2);
-
-            long startTimeWallClock =
-                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
-            long endTimeWallClock =
-                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
-
-            assertThat(Instant.ofEpochMilli(startTimeWallClock))
-                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
-            assertThat(Instant.ofEpochMilli(endTimeWallClock))
-                    .isEqualTo(Instant.parse("2023-01-02T04:30:00Z"));
-
-            consumer.accept(
-                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
-            return null;
-        }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(),
-                any(Consumer.class));
-
-        mPowerStatsScheduler.start(/*enabled*/ true);
-        ConditionVariable done = new ConditionVariable();
-        mHandler.post(done::open);
-        done.block();
-
-        assertThat(mPowerStatsCollectionOccurred).isTrue();
-        assertThat(mScheduledAlarms).containsExactly(
-                START_REALTIME + TimeUnit.MINUTES.toMillis(90) + TimeUnit.HOURS.toMillis(1));
-
-        verify(mPowerStatsAggregator, times(2))
-                .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class));
-
-        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
-        assertThat(contents).hasSize(3);
-        // Skip the first entry, which was placed in the store at the beginning of this test
-        PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0);
-        PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0);
-        assertThat(timeFrame1.startMonotonicTime).isEqualTo(START_REALTIME + 123);
-        assertThat(timeFrame2.startMonotonicTime)
-                .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration);
-        assertThat(Instant.ofEpochMilli(timeFrame2.startTime))
-                .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
-        assertThat(Duration.ofMillis(timeFrame2.duration)).isEqualTo(Duration.ofMinutes(30));
-    }
-
-    private AggregatedPowerStats createAggregatedPowerStats(long monotonicTime, long currentTime,
-            long duration) {
-        AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig);
-        stats.addClockUpdate(monotonicTime, currentTime);
-        stats.setDuration(duration);
-        return stats;
-    }
-
     @Test
     public void alignToWallClock() {
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+
         // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min
         assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15),
                 123 + TimeUnit.HOURS.toMillis(2),
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
index 36d7af5..dc8d920 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
@@ -59,14 +59,7 @@
         clearDirectory(mStoreDirectory);
 
         mPowerStatsStore = new PowerStatsStore(mStoreDirectory,
-                MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES,
-                new TestHandler(),
-                (sectionType, parser) -> {
-                    if (sectionType.equals(TestSection.TYPE)) {
-                        return TestSection.readXml(parser);
-                    }
-                    return null;
-                });
+                MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES, new TestHandler());
     }
 
     @Test
@@ -144,7 +137,7 @@
         }
 
         @Override
-        void write(TypedXmlSerializer serializer) throws IOException {
+        public void write(TypedXmlSerializer serializer) throws IOException {
             StringBuilder sb = new StringBuilder();
             for (int i = 0; i < mSize; i++) {
                 sb.append("X");
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java
index 817fdcb..8c09d1d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.when;
 
+import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
@@ -32,6 +33,7 @@
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
 import com.android.server.power.stats.ScreenPowerStatsCollector.Injector;
+import com.android.server.power.stats.format.ScreenPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -39,8 +41,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.function.IntSupplier;
-
 public class ScreenPowerStatsCollectorTest {
     private static final int APP_UID1 = 42;
     private static final int APP_UID2 = 24;
@@ -89,11 +89,6 @@
         }
 
         @Override
-        public IntSupplier getVoltageSupplier() {
-            return () -> 3500;
-        }
-
-        @Override
         public int getDisplayCount() {
             return 2;
         }
@@ -115,6 +110,7 @@
                 return uid;
             }
         });
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(3500);
     }
 
     @Test
@@ -125,8 +121,8 @@
         // Establish a baseline
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
                 .thenReturn(new int[]{77});
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
-                .thenReturn(new long[]{10_000});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{77}))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(10_000)});
 
         doAnswer(inv -> {
             ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback =
@@ -139,8 +135,8 @@
 
         collector.collectStats();
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
-                .thenReturn(new long[]{45_000});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{77}))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(45_000)});
         when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0))
                 .thenReturn(60_000L);
         when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
@@ -171,8 +167,7 @@
 
         PowerStats powerStats = collector.collectStats();
 
-        ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout();
-        layout.fromExtras(powerStats.descriptor.extras);
+        ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(powerStats.descriptor);
 
         // (45000 - 10000) / 3500
         assertThat(layout.getConsumedEnergy(powerStats.stats, 0))
@@ -204,4 +199,10 @@
         assertThat(layout.getUidTopActivityDuration(powerStats.uidStats.get(APP_UID2)))
                 .isEqualTo(10000);
     }
+
+    private EnergyConsumerResult mockEnergyConsumer(long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        return ecr;
+    }
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
index b13fc53..8b5e6ee 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
@@ -32,6 +32,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.net.NetworkStats;
 import android.net.wifi.WifiManager;
@@ -47,6 +48,7 @@
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.format.WifiPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -57,7 +59,6 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
 public class WifiPowerStatsCollectorTest {
@@ -154,11 +155,6 @@
         }
 
         @Override
-        public IntSupplier getVoltageSupplier() {
-            return () -> 3500;
-        }
-
-        @Override
         public Supplier<NetworkStats> getWifiNetworkStatsSupplier() {
             return mNetworkStatsSupplier;
         }
@@ -368,6 +364,7 @@
         WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, null);
         collector.setEnabled(true);
 
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(3500);
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
                 .thenReturn(new int[]{777});
 
@@ -385,8 +382,8 @@
         mockWifiScanTimes(APP_UID2, 3000, 4000);
         mockWifiScanTimes(ISOLATED_UID, 5000, 6000);
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777})))
-                .thenReturn(new long[]{10000});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(eq(new int[]{777})))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(10_000)});
 
         collector.collectStats();
 
@@ -404,13 +401,19 @@
         mockWifiScanTimes(APP_UID2, 3100, 4200);
         mockWifiScanTimes(ISOLATED_UID, 5300, 6400);
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777})))
-                .thenReturn(new long[]{64321});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(eq(new int[]{777})))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(64_321)});
 
         mStatsRule.setTime(20000, 20000);
         return collector.collectStats();
     }
 
+    private EnergyConsumerResult mockEnergyConsumer(long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        return ecr;
+    }
+
     private void mockWifiActivityInfo(long timestamp, long rxTimeMs, long txTimeMs, int scanTimeMs,
             int idleTimeMs) {
         int stackState = 0;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java
similarity index 93%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java
index 04d53de..0e73329 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AggregatedPowerStatsTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -49,7 +49,8 @@
     private static final int COMPONENT_STATE_1 = 1;
     private static final int COMPONENT_STATE_2 = 2;
 
-    private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+    private AggregatedPowerStatsConfig
+            mAggregatedPowerStatsConfig;
     private PowerStats.Descriptor mPowerComponentDescriptor;
 
     @Before
@@ -67,7 +68,8 @@
         mAggregatedPowerStatsConfig.trackCustomPowerComponents(
                         () -> new PowerStatsProcessor() {
                             @Override
-                            void finish(PowerComponentAggregatedPowerStats stats,
+                            void finish(
+                                    PowerComponentAggregatedPowerStats stats,
                                     long timestampMs) {
                             }
                         })
@@ -103,8 +105,8 @@
 
         TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(new ByteArrayInputStream(baos.toByteArray()), "UTF-8");
-        AggregatedPowerStats actualStats = AggregatedPowerStats.createFromXml(parser,
-                mAggregatedPowerStatsConfig);
+        AggregatedPowerStats actualStats =
+                AggregatedPowerStats.createFromXml(parser, mAggregatedPowerStatsConfig);
 
         verifyAggregatedPowerStats(actualStats);
     }
@@ -163,7 +165,8 @@
         return stats;
     }
 
-    private void verifyAggregatedPowerStats(AggregatedPowerStats stats) {
+    private void verifyAggregatedPowerStats(
+            AggregatedPowerStats stats) {
         PowerStats.Descriptor descriptor = stats.getPowerComponentStats(TEST_POWER_COMPONENT)
                 .getPowerStatsDescriptor();
         assertThat(descriptor.powerComponentId).isEqualTo(TEST_POWER_COMPONENT);
@@ -277,7 +280,8 @@
                 .isEqualTo(new long[]{250, 300});
     }
 
-    private static long[] getDeviceStats(AggregatedPowerStats stats, int powerComponentId,
+    private static long[] getDeviceStats(
+            AggregatedPowerStats stats, int powerComponentId,
             int... states) {
         PowerComponentAggregatedPowerStats powerComponentStats =
                 stats.getPowerComponentStats(powerComponentId);
@@ -286,7 +290,8 @@
         return out;
     }
 
-    private static long[] getStateStats(AggregatedPowerStats stats, int key, int... states) {
+    private static long[] getStateStats(
+            AggregatedPowerStats stats, int key, int... states) {
         PowerComponentAggregatedPowerStats powerComponentStats =
                 stats.getPowerComponentStats(TEST_POWER_COMPONENT);
         long[] out = new long[powerComponentStats.getPowerStatsDescriptor().stateStatsArrayLength];
@@ -294,7 +299,8 @@
         return out;
     }
 
-    private static long[] getUidDeviceStats(AggregatedPowerStats stats, int powerComponentId,
+    private static long[] getUidDeviceStats(
+            AggregatedPowerStats stats, int powerComponentId,
             int uid, int... states) {
         PowerComponentAggregatedPowerStats powerComponentStats =
                 stats.getPowerComponentStats(powerComponentId);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java
similarity index 68%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java
index a2a7e00..21e615f 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/AmbientDisplayPowerStatsProcessorTest.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -35,7 +35,12 @@
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
-import com.android.server.power.stats.ScreenPowerStatsCollector.Injector;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.PowerStatsCollector;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.ScreenPowerStatsCollector;
+import com.android.server.power.stats.ScreenPowerStatsCollector.ScreenUsageTimeRetriever;
+import com.android.server.power.stats.format.PowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -43,8 +48,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.function.IntSupplier;
-
 public class AmbientDisplayPowerStatsProcessorTest {
 
     @Rule(order = 0)
@@ -64,49 +67,45 @@
     @Mock
     private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
     @Mock
-    private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
+    private ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
 
-    private final Injector mInjector = new Injector() {
-        @Override
-        public Handler getHandler() {
-            return mStatsRule.getHandler();
-        }
+    private final ScreenPowerStatsCollector.Injector mInjector =
+            new ScreenPowerStatsCollector.Injector() {
+                @Override
+                public Handler getHandler() {
+                    return mStatsRule.getHandler();
+                }
 
-        @Override
-        public Clock getClock() {
-            return mStatsRule.getMockClock();
-        }
+                @Override
+                public Clock getClock() {
+                    return mStatsRule.getMockClock();
+                }
 
-        @Override
-        public PowerStatsUidResolver getUidResolver() {
-            return new PowerStatsUidResolver();
-        }
+                @Override
+                public PowerStatsUidResolver getUidResolver() {
+                    return new PowerStatsUidResolver();
+                }
 
-        @Override
-        public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
-            return 0;
-        }
+                @Override
+                public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+                    return 0;
+                }
 
-        @Override
-        public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
-            return mConsumedEnergyRetriever;
-        }
+                @Override
+                public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+                    return mConsumedEnergyRetriever;
+                }
 
-        @Override
-        public IntSupplier getVoltageSupplier() {
-            return () -> VOLTAGE_MV;
-        }
+                @Override
+                public int getDisplayCount() {
+                    return 2;
+                }
 
-        @Override
-        public int getDisplayCount() {
-            return 2;
-        }
-
-        @Override
-        public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
-            return mScreenUsageTimeRetriever;
-        }
-    };
+                @Override
+                public ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
+                    return mScreenUsageTimeRetriever;
+                }
+            };
 
     @Before
     public void setup() {
@@ -167,7 +166,8 @@
         return stats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY);
     }
 
-    private void assertPowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats,
+    private void assertPowerEstimate(
+            PowerComponentAggregatedPowerStats aggregatedStats,
             int powerState, int screenState, double expectedPowerEstimate) {
         PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor();
         PowerStatsLayout layout = new PowerStatsLayout(descriptor);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BinaryStatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java
similarity index 92%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/BinaryStatePowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java
index 4b40f68..b412ad6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BinaryStatePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BinaryStatePowerStatsProcessorTest.java
@@ -14,19 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -40,6 +40,9 @@
 
 import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.MockClock;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.format.BinaryStatePowerStatsLayout;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -146,7 +149,8 @@
 
     @Test
     public void energyConsumerModel() {
-        BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+        BinaryStatePowerStatsLayout
+                statsLayout = new BinaryStatePowerStatsLayout();
         PersistableBundle extras = new PersistableBundle();
         statsLayout.toExtras(extras);
         PowerStats.Descriptor descriptor = new PowerStats.Descriptor(POWER_COMPONENT,
@@ -270,9 +274,8 @@
                 .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
                 .setProcessorSupplier(processorSupplier);
 
-        AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
         PowerComponentAggregatedPowerStats powerComponentStats =
-                aggregatedPowerStats.getPowerComponentStats(POWER_COMPONENT);
+                new AggregatedPowerStats(config).getPowerComponentStats(POWER_COMPONENT);
         powerComponentStats.start(0);
 
         powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java
similarity index 91%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java
index 4a8125f..6dfc220 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BluetoothPowerStatsProcessorTest.java
@@ -14,19 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -38,6 +38,7 @@
 import android.bluetooth.UidTraffic;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 import android.os.Handler;
@@ -48,7 +49,12 @@
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerProfile;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.BluetoothPowerStatsCollector;
 import com.android.server.power.stats.BluetoothPowerStatsCollector.BluetoothStatsRetriever;
+import com.android.server.power.stats.PowerStatsCollector;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.format.BluetoothPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -58,7 +64,6 @@
 
 import java.util.List;
 import java.util.concurrent.Executor;
-import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
 public class BluetoothPowerStatsProcessorTest {
@@ -143,11 +148,6 @@
                 }
 
                 @Override
-                public IntSupplier getVoltageSupplier() {
-                    return () -> VOLTAGE_MV;
-                }
-
-                @Override
                 public BluetoothStatsRetriever getBluetoothStatsRetriever() {
                     return mBluetoothStatsRetriever;
                 }
@@ -363,7 +363,8 @@
 
     @Test
     public void consumedEnergyModel() {
-        // No power monitoring hardware
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
+        // Power monitoring hardware exists
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH))
                 .thenReturn(new int[]{BLUETOOTH_ENERGY_CONSUMER_ID});
 
@@ -378,8 +379,8 @@
 
         mUidScanTimes.put(APP_UID1, 100);
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
-                new int[]{BLUETOOTH_ENERGY_CONSUMER_ID})).thenReturn(new long[]{0});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{BLUETOOTH_ENERGY_CONSUMER_ID}))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(0)});
 
         aggregatedStats.start(0);
 
@@ -404,8 +405,8 @@
 
         // 10 mAh represented as microWattSeconds
         long energyUws = 10 * 3600 * VOLTAGE_MV;
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
-                new int[]{BLUETOOTH_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{BLUETOOTH_ENERGY_CONSUMER_ID}))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(energyUws)});
 
         aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
 
@@ -468,16 +469,15 @@
 
     private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
             Supplier<PowerStatsProcessor> processorSupplier) {
-        AggregatedPowerStatsConfig.PowerComponent config =
-                new AggregatedPowerStatsConfig.PowerComponent(
-                        BatteryConsumer.POWER_COMPONENT_BLUETOOTH)
-                        .trackDeviceStates(STATE_POWER, STATE_SCREEN)
-                        .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
-                        .setProcessorSupplier(processorSupplier);
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)
+                .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+                .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+                .setProcessorSupplier(processorSupplier);
 
         PowerComponentAggregatedPowerStats aggregatedStats =
-                new PowerComponentAggregatedPowerStats(
-                        new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+                new AggregatedPowerStats(config).getPowerComponentStats(
+                        BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
 
         aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
         aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
@@ -491,6 +491,12 @@
         return states;
     }
 
+    private EnergyConsumerResult mockEnergyConsumer(long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        return ecr;
+    }
+
     private BluetoothActivityEnergyInfo mockBluetoothActivityEnergyInfo(long timestamp,
             long rxTimeMs, long txTimeMs, long idleTimeMs, UidTraffic... uidTraffic) {
         if (RavenwoodRule.isOnRavenwood()) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java
similarity index 88%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java
index 88a4f5e..0afcbf1 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CameraPowerStatsTest.java
@@ -14,23 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
@@ -46,6 +45,12 @@
 import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.CameraPowerStatsCollector;
+import com.android.server.power.stats.EnergyConsumerPowerStatsCollector;
+import com.android.server.power.stats.PowerStatsCollector;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.format.BinaryStatePowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -53,7 +58,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
 public class CameraPowerStatsTest {
@@ -103,11 +107,6 @@
                 public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
                     return mConsumedEnergyRetriever;
                 }
-
-                @Override
-                public IntSupplier getVoltageSupplier() {
-                    return () -> VOLTAGE_MV;
-                }
             };
 
     private MonotonicClock mMonotonicClock;
@@ -120,8 +119,9 @@
 
     @Test
     public void energyConsumerModel() {
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
         when(mConsumedEnergyRetriever
-                .getEnergyConsumerIds(eq((int) EnergyConsumerType.CAMERA), any()))
+                .getEnergyConsumerIds(eq((int) EnergyConsumerType.CAMERA)))
                 .thenReturn(new int[]{ENERGY_CONSUMER_ID});
 
         PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
@@ -161,8 +161,7 @@
         stats.finish(11_000);
 
         PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
-        BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
-        statsLayout.fromExtras(descriptor.extras);
+        BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout(descriptor);
 
         // Total estimated power = 3,600,000 uC = 1.0 mAh
         // of which 3,000,000 is distributed:
@@ -243,7 +242,8 @@
 
     private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
             Supplier<PowerStatsProcessor> processorSupplier) {
-        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        AggregatedPowerStatsConfig
+                config = new AggregatedPowerStatsConfig();
         config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CAMERA)
                 .trackDeviceStates(
                         AggregatedPowerStatsConfig.STATE_POWER,
@@ -254,9 +254,8 @@
                         AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
                 .setProcessorSupplier(processorSupplier);
 
-        AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
-        PowerComponentAggregatedPowerStats powerComponentStats =
-                aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_CAMERA);
+        PowerComponentAggregatedPowerStats powerComponentStats = new AggregatedPowerStats(config)
+                .getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_CAMERA);
         powerComponentStats.start(0);
 
         powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java
similarity index 86%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java
index ab2e631..6938615 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CpuPowerStatsProcessorTest.java
@@ -13,24 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
 
 import android.os.BatteryConsumer;
 import android.os.PersistableBundle;
@@ -42,6 +41,8 @@
 
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.format.CpuPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -202,26 +203,18 @@
             PowerComponentAggregatedPowerStats {
         private final CpuPowerStatsLayout mStatsLayout;
         private final PowerStats.Descriptor mDescriptor;
-        private HashMap<String, long[]> mDeviceStats = new HashMap<>();
-        private HashMap<String, long[]> mUidStats = new HashMap<>();
-        private HashSet<Integer> mUids = new HashSet<>();
-        private HashMap<String, Double> mExpectedDevicePower = new HashMap<>();
-        private HashMap<String, Double> mExpectedUidPower = new HashMap<>();
+        private final HashMap<String, long[]> mDeviceStats = new HashMap<>();
+        private final HashMap<String, long[]> mUidStats = new HashMap<>();
+        private final HashSet<Integer> mUids = new HashSet<>();
+        private final HashMap<String, Double> mExpectedDevicePower = new HashMap<>();
+        private final HashMap<String, Double> mExpectedUidPower = new HashMap<>();
 
-        MockPowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config,
+        MockPowerComponentAggregatedPowerStats(
+                AggregatedPowerStatsConfig.PowerComponent config,
                 boolean useEnergyConsumers) {
-            super(new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
-            mStatsLayout = new CpuPowerStatsLayout();
-            mStatsLayout.addDeviceSectionCpuTimeByScalingStep(3);
-            mStatsLayout.addDeviceSectionCpuTimeByCluster(2);
-            mStatsLayout.addDeviceSectionUsageDuration();
-            if (useEnergyConsumers) {
-                mStatsLayout.addDeviceSectionEnergyConsumers(2);
-            }
-            mStatsLayout.addDeviceSectionPowerEstimate();
-            mStatsLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0, 1, 2});
-            mStatsLayout.addUidSectionPowerEstimate();
-
+            super(new AggregatedPowerStats(new AggregatedPowerStatsConfig()), config);
+            mStatsLayout = new CpuPowerStatsLayout(useEnergyConsumers ? 2 : 0, 2,
+                    new int[]{0, 1, 2});
             PersistableBundle extras = new PersistableBundle();
             mStatsLayout.toExtras(extras);
             mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java
similarity index 90%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java
index 8239fdb..42baba7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/CustomEnergyConsumerPowerStatsTest.java
@@ -14,20 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -42,6 +42,12 @@
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.CustomEnergyConsumerPowerStatsCollector;
+import com.android.server.power.stats.EnergyConsumerPowerStatsCollector;
+import com.android.server.power.stats.PowerStatsCollector;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.format.EnergyConsumerPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -54,7 +60,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
-import java.util.function.IntSupplier;
 
 public class CustomEnergyConsumerPowerStatsTest {
     @Rule(order = 0)
@@ -105,11 +110,6 @@
                 public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
                     return mConsumedEnergyRetriever;
                 }
-
-                @Override
-                public IntSupplier getVoltageSupplier() {
-                    return () -> VOLTAGE_MV;
-                }
             };
 
 
@@ -237,6 +237,7 @@
 
     private List<PowerStats> collectPowerStats(long timestamp, int chargeUc1, int chargeUc2,
             EnergyConsumerAttribution... attributions2) throws Exception {
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.OTHER))
                 .thenReturn(new int[]{ENERGY_CONSUMER_ID1, ENERGY_CONSUMER_ID2});
         when(mConsumedEnergyRetriever.getEnergyConsumerName(ENERGY_CONSUMER_ID1))
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java
similarity index 64%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java
index 127ab8a..e6207d4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/GnssPowerStatsTest.java
@@ -14,23 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
@@ -47,6 +46,12 @@
 import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.EnergyConsumerPowerStatsCollector;
+import com.android.server.power.stats.GnssPowerStatsCollector;
+import com.android.server.power.stats.PowerStatsCollector;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.format.BinaryStatePowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -54,7 +59,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
 public class GnssPowerStatsTest {
@@ -74,6 +78,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
@@ -105,31 +110,27 @@
                 public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
                     return mConsumedEnergyRetriever;
                 }
-
-                @Override
-                public IntSupplier getVoltageSupplier() {
-                    return () -> VOLTAGE_MV;
-                }
             };
 
     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
     public void powerProfileModel() {
         // ODPM unsupported
         when(mConsumedEnergyRetriever
-                .getEnergyConsumerIds(eq((int) EnergyConsumerType.GNSS), any()))
+                .getEnergyConsumerIds(eq((int) EnergyConsumerType.GNSS)))
                 .thenReturn(new int[0]);
 
         PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
                 () -> new GnssPowerStatsProcessor(mStatsRule.getPowerProfile(), mUidResolver));
-        stats.start(0);
 
         GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
         collector.addConsumer(
@@ -142,9 +143,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,11 +161,89 @@
         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);
+        BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout(descriptor);
+
+        // 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)))
+                .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(descriptor);
 
         // scr-on, GNSS-good: 2500 * 100 = 250000 mA-ms = 0.06944 mAh
         // scr-off GNSS=good: 4500 * 100 = 0.12500 mAh
@@ -212,8 +293,9 @@
 
     @Test
     public void energyConsumerModel() {
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
         when(mConsumedEnergyRetriever
-                .getEnergyConsumerIds(eq((int) EnergyConsumerType.GNSS), any()))
+                .getEnergyConsumerIds(eq((int) EnergyConsumerType.GNSS)))
                 .thenReturn(new int[]{ENERGY_CONSUMER_ID});
 
         PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(
@@ -224,8 +306,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 +314,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,20 +327,17 @@
         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();
-        statsLayout.fromExtras(descriptor.extras);
+        BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout(descriptor);
 
         // Total estimated power = 3,600,000 uC = 1.0 mAh
         // of which 3,000,000 is distributed:
@@ -313,33 +392,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) {
@@ -348,7 +439,8 @@
 
     private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
             Supplier<PowerStatsProcessor> processorSupplier) {
-        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        AggregatedPowerStatsConfig
+                config = new AggregatedPowerStatsConfig();
         config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_GNSS)
                 .trackDeviceStates(
                         AggregatedPowerStatsConfig.STATE_POWER,
@@ -359,15 +451,16 @@
                         AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
                 .setProcessorSupplier(processorSupplier);
 
-        AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
-        PowerComponentAggregatedPowerStats powerComponentStats =
-                aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_GNSS);
-        powerComponentStats.start(0);
+        PowerComponentAggregatedPowerStats powerComponentStats = new AggregatedPowerStats(config)
+                .getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_GNSS);
+        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/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java
similarity index 87%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java
index 89d59a9..80358c5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.METERED_NO;
@@ -23,12 +23,12 @@
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,6 +41,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -53,6 +54,11 @@
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.MobileRadioPowerStatsCollector;
+import com.android.server.power.stats.PowerStatsCollector;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.format.MobileRadioPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -61,7 +67,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
-import java.util.function.IntSupplier;
 import java.util.function.LongSupplier;
 import java.util.function.Supplier;
 
@@ -129,11 +134,6 @@
                 }
 
                 @Override
-                public IntSupplier getVoltageSupplier() {
-                    return () -> VOLTAGE_MV;
-                }
-
-                @Override
                 public Supplier<NetworkStats> getMobileNetworkStatsSupplier() {
                     return mNetworkStatsSupplier;
                 }
@@ -172,17 +172,15 @@
 
         mStatsRule.setTestPowerProfile("power_profile_test_modem_calculator");
 
-        AggregatedPowerStatsConfig.PowerComponent config =
-                new AggregatedPowerStatsConfig.PowerComponent(
-                        BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
-                        .trackDeviceStates(STATE_POWER, STATE_SCREEN)
-                        .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
-                        .setProcessorSupplier(() -> new MobileRadioPowerStatsProcessor(
-                                mStatsRule.getPowerProfile()));
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+                .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+                .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile()));
 
-        PowerComponentAggregatedPowerStats aggregatedStats =
-                new PowerComponentAggregatedPowerStats(
-                        new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+        PowerComponentAggregatedPowerStats aggregatedStats = new AggregatedPowerStats(config)
+                .getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
 
         aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
         aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
@@ -232,8 +230,7 @@
         aggregatedStats.finish(10_000);
 
         MobileRadioPowerStatsLayout statsLayout =
-                new MobileRadioPowerStatsLayout(
-                        aggregatedStats.getPowerStatsDescriptor());
+                new MobileRadioPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
 
         //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
         // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
@@ -316,8 +313,7 @@
                 prepareAggregatedStats_energyConsumerModel();
 
         MobileRadioPowerStatsLayout statsLayout =
-                new MobileRadioPowerStatsLayout(
-                        aggregatedStats.getPowerStatsDescriptor());
+                new MobileRadioPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor());
 
         // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
         double totalPower = 0;
@@ -406,23 +402,22 @@
 
     private PowerComponentAggregatedPowerStats prepareAggregatedStats_energyConsumerModel() {
         // PowerStats hardware is available
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
                 .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID});
 
         mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
                 .initMeasuredEnergyStatsLocked();
 
-        AggregatedPowerStatsConfig.PowerComponent config =
-                new AggregatedPowerStatsConfig.PowerComponent(
-                        BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
-                        .trackDeviceStates(STATE_POWER, STATE_SCREEN)
-                        .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
-                        .setProcessorSupplier(() -> new MobileRadioPowerStatsProcessor(
-                                mStatsRule.getPowerProfile()));
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+                .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+                .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+                .setProcessorSupplier(
+                        () -> new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile()));
 
-        PowerComponentAggregatedPowerStats aggregatedStats =
-                new PowerComponentAggregatedPowerStats(
-                        new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+        PowerComponentAggregatedPowerStats aggregatedStats = new AggregatedPowerStats(config)
+                .getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
 
         aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
         aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
@@ -436,9 +431,9 @@
         // Initial empty ModemActivityInfo.
         mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+        when(mConsumedEnergyRetriever.getConsumedEnergy(
                 new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID}))
-                .thenReturn(new long[]{0});
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(0)});
 
         aggregatedStats.start(0);
 
@@ -467,8 +462,8 @@
         mStatsRule.setTime(10_000, 10_000);
 
         long energyUws = 10_000_000L * VOLTAGE_MV / 1000L;
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
-                new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID}))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(energyUws)});
 
         when(mCallDurationSupplier.getAsLong()).thenReturn(200L);
         when(mScanDurationSupplier.getAsLong()).thenReturn(5555L);
@@ -485,6 +480,12 @@
         return states;
     }
 
+    private EnergyConsumerResult mockEnergyConsumer(long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        return ecr;
+    }
+
     private void mockModemActivityInfo(ModemActivityInfo emptyMai) {
         doAnswer(invocation -> {
             OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStatePowerAttributorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStatePowerAttributorTest.java
new file mode 100644
index 0000000..704ee62
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStatePowerAttributorTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.power.stats.processor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.MonotonicClock;
+import com.android.server.power.stats.MockClock;
+import com.android.server.power.stats.PowerStatsScheduler;
+import com.android.server.power.stats.PowerStatsSpan;
+import com.android.server.power.stats.PowerStatsStore;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+public class MultiStatePowerAttributorTest {
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
+    private PowerStatsStore mPowerStatsStore;
+    private Handler mHandler;
+    private final MockClock mClock = new MockClock();
+    private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
+    private PowerStatsScheduler mPowerStatsScheduler;
+    private PowerStatsAggregator mPowerStatsAggregator;
+    private MultiStatePowerAttributor mPowerAttributor;
+    private final List<Long> mScheduledAlarms = new ArrayList<>();
+    private boolean mPowerStatsCollectionOccurred;
+
+    private static final int START_REALTIME = 7654321;
+
+    @Before
+    public void setup() throws IOException {
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+
+        mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
+        mClock.realtime = START_REALTIME;
+
+        HandlerThread bgThread = new HandlerThread("bg thread");
+        bgThread.start();
+        mHandler = new Handler(bgThread.getLooper());
+        mPowerStatsStore = new PowerStatsStore(
+                Files.createTempDirectory("MultiStatePowerAttributorTest").toFile(), mHandler);
+        mPowerStatsAggregator = mock(PowerStatsAggregator.class);
+        mPowerAttributor = new MultiStatePowerAttributor(mPowerStatsStore, mPowerStatsAggregator);
+        mPowerStatsScheduler = new PowerStatsScheduler(
+                () -> mPowerStatsCollectionOccurred = true,
+                mock(BatteryStatsHistory.class),
+                mPowerAttributor, TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1),
+                mPowerStatsStore,
+                ((triggerAtMillis, tag, onAlarmListener, handler) ->
+                        mScheduledAlarms.add(triggerAtMillis)),
+                mClock, mMonotonicClock, () -> 12345L, mHandler);
+    }
+
+    @Test
+    public void storeAggregatedPowerStats() {
+        mPowerStatsStore.reset();
+
+        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty();
+
+        mPowerAttributor.storeAggregatedPowerStats(
+                createAggregatedPowerStats(mMonotonicClock.monotonicTime(), mClock.currentTime,
+                        123));
+
+        long delayBeforeAggregating = TimeUnit.MINUTES.toMillis(90);
+        mClock.realtime += delayBeforeAggregating;
+        mClock.currentTime += delayBeforeAggregating;
+
+        doAnswer(invocation -> {
+            // The first span is longer than 30 min, because the end time is being aligned with
+            // the wall clock.  Subsequent spans should be precisely 30 minutes.
+            long startTime = invocation.getArgument(1);
+            long endTime = invocation.getArgument(2);
+            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(3);
+
+            long startTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+            long endTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+            assertThat(startTime).isEqualTo(START_REALTIME + 123);
+            assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30));
+            assertThat(Instant.ofEpochMilli(endTimeWallClock))
+                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+
+            consumer.accept(
+                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+            return null;
+        }).doAnswer(invocation -> {
+            long startTime = invocation.getArgument(1);
+            long endTime = invocation.getArgument(2);
+            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(3);
+
+            long startTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime);
+            long endTimeWallClock =
+                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
+
+            assertThat(Instant.ofEpochMilli(startTimeWallClock))
+                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+            assertThat(Instant.ofEpochMilli(endTimeWallClock))
+                    .isEqualTo(Instant.parse("2023-01-02T04:30:00Z"));
+
+            consumer.accept(
+                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime));
+            return null;
+        }).when(mPowerStatsAggregator).aggregatePowerStats(any(BatteryStatsHistory.class),
+                anyLong(), anyLong(), any(Consumer.class));
+
+        mPowerStatsScheduler.start(/*enabled*/ true);
+        ConditionVariable done = new ConditionVariable();
+        mHandler.post(done::open);
+        done.block();
+
+        assertThat(mPowerStatsCollectionOccurred).isTrue();
+        assertThat(mScheduledAlarms).containsExactly(
+                START_REALTIME + TimeUnit.MINUTES.toMillis(90) + TimeUnit.HOURS.toMillis(1));
+
+        verify(mPowerStatsAggregator, times(2)).aggregatePowerStats(
+                any(BatteryStatsHistory.class), anyLong(), anyLong(), any(Consumer.class));
+
+        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents();
+        assertThat(contents).hasSize(3);
+        // Skip the first entry, which was placed in the store at the beginning of this test
+        PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0);
+        PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0);
+        assertThat(timeFrame1.startMonotonicTime).isEqualTo(START_REALTIME + 123);
+        assertThat(timeFrame2.startMonotonicTime)
+                .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration);
+        assertThat(Instant.ofEpochMilli(timeFrame2.startTime))
+                .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
+        assertThat(Duration.ofMillis(timeFrame2.duration)).isEqualTo(Duration.ofMinutes(30));
+    }
+
+    private AggregatedPowerStats createAggregatedPowerStats(long monotonicTime, long currentTime,
+            long duration) {
+        AggregatedPowerStats stats = new AggregatedPowerStats(new AggregatedPowerStatsConfig());
+        stats.addClockUpdate(monotonicTime, currentTime);
+        stats.setDuration(duration);
+        return stats;
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java
similarity index 99%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java
index ae258cd3..a232c0c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MultiStateStatsTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -198,8 +198,7 @@
                 new MultiStateStats.States("scr", trackScreenState, "screen-off", "plugged-in"));
     }
 
-    private FactorySubject assertThatCpuPerformanceStatsFactory(
-            MultiStateStats.Factory factory) {
+    private FactorySubject assertThatCpuPerformanceStatsFactory(MultiStateStats.Factory factory) {
         FactorySubject subject = new FactorySubject();
         subject.mFactory = factory;
         return subject;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java
similarity index 84%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java
index cb1bcfe..535f2da 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,6 +41,11 @@
 import android.telephony.TelephonyManager;
 
 import com.android.internal.os.Clock;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.MobileRadioPowerStatsCollector;
+import com.android.server.power.stats.PowerStatsCollector;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.format.PowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -48,7 +53,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.function.IntSupplier;
 import java.util.function.LongSupplier;
 import java.util.function.Supplier;
 
@@ -113,11 +117,6 @@
                 }
 
                 @Override
-                public IntSupplier getVoltageSupplier() {
-                    return () -> VOLTAGE_MV;
-                }
-
-                @Override
                 public Supplier<NetworkStats> getMobileNetworkStatsSupplier() {
                     return mNetworkStatsSupplier;
                 }
@@ -156,19 +155,17 @@
 
     @Test
     public void copyEstimatesFromMobileRadioPowerStats() {
-
-        AggregatedPowerStatsConfig aggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
-        aggregatedPowerStatsConfig.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
                 .trackDeviceStates(STATE_POWER, STATE_SCREEN)
                 .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
                 .setProcessorSupplier(
                         () -> new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile()));
-        aggregatedPowerStatsConfig.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_PHONE,
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_PHONE,
                         BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
                 .setProcessorSupplier(PhoneCallPowerStatsProcessor::new);
 
-        AggregatedPowerStats aggregatedPowerStats =
-                new AggregatedPowerStats(aggregatedPowerStatsConfig);
+        AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
         PowerComponentAggregatedPowerStats mobileRadioStats =
                 aggregatedPowerStats.getPowerComponentStats(
                         BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
@@ -208,8 +205,7 @@
                 aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_PHONE);
         stats.finish(10_000);
 
-        PowerStatsLayout statsLayout =
-                new PowerStatsLayout(stats.getPowerStatsDescriptor());
+        PowerStatsLayout statsLayout = new PowerStatsLayout(stats.getPowerStatsDescriptor());
 
         long[] deviceStats = new long[stats.getPowerStatsDescriptor().statsArrayLength];
         stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsAggregatorTest.java
similarity index 95%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsAggregatorTest.java
index 3929137..f312bed 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsAggregatorTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -32,6 +32,7 @@
 import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.MockClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -53,7 +54,7 @@
     private final MockClock mClock = new MockClock();
     private final MonotonicClock mMonotonicClock = new MonotonicClock(START_TIME, mClock);
     private BatteryStatsHistory mHistory;
-    private PowerStatsAggregator mAggregator;
+    private com.android.server.power.stats.processor.PowerStatsAggregator mAggregator;
     private int mAggregatedStatsCount;
 
     @Before
@@ -71,7 +72,7 @@
                         AggregatedPowerStatsConfig.STATE_POWER,
                         AggregatedPowerStatsConfig.STATE_SCREEN,
                         AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
-        mAggregator = new PowerStatsAggregator(config, mHistory);
+        mAggregator = new PowerStatsAggregator(config);
     }
 
     @Test
@@ -119,7 +120,7 @@
         powerStats.uidStats.put(TEST_UID, new long[]{4444});
         mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats);
 
-        mAggregator.aggregatePowerStats(0, MonotonicClock.UNDEFINED, stats -> {
+        mAggregator.aggregatePowerStats(mHistory, 0, MonotonicClock.UNDEFINED, stats -> {
             assertThat(mAggregatedStatsCount++).isEqualTo(0);
             assertThat(stats.getStartTime()).isEqualTo(START_TIME);
 
@@ -138,7 +139,8 @@
 
             long[] values = new long[1];
 
-            PowerComponentAggregatedPowerStats powerComponentStats = stats.getPowerComponentStats(
+            PowerComponentAggregatedPowerStats
+                    powerComponentStats = stats.getPowerComponentStats(
                     TEST_POWER_COMPONENT);
 
             assertThat(powerComponentStats.getDeviceStats(values, new int[]{
@@ -218,7 +220,7 @@
 
         mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true);
 
-        mAggregator.aggregatePowerStats(0, MonotonicClock.UNDEFINED, stats -> {
+        mAggregator.aggregatePowerStats(mHistory, 0, MonotonicClock.UNDEFINED, stats -> {
             long[] values = new long[1];
 
             PowerComponentAggregatedPowerStats powerComponentStats =
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
similarity index 95%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
index 96203a5..024743d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.mock;
 
 import android.annotation.NonNull;
+import android.content.Context;
 import android.os.AggregateBatteryConsumer;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
@@ -37,9 +38,16 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.CpuScalingPolicies;
 import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.MockClock;
+import com.android.server.power.stats.PowerStatsStore;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.format.CpuPowerStatsLayout;
+import com.android.server.power.stats.format.EnergyConsumerPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -76,6 +84,7 @@
     private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
     private PowerStatsStore mPowerStatsStore;
     private PowerStatsAggregator mPowerStatsAggregator;
+    private MultiStatePowerAttributor mPowerAttributor;
     private BatteryStatsHistory mHistory;
     private CpuPowerStatsLayout mCpuStatsArrayLayout;
     private PowerStats.Descriptor mPowerStatsDescriptor;
@@ -108,25 +117,23 @@
                         AggregatedPowerStatsConfig.STATE_SCREEN,
                         AggregatedPowerStatsConfig.STATE_PROCESS_STATE);
 
-        mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler(), config);
+        mPowerStatsStore = new PowerStatsStore(storeDirectory, new TestHandler());
         mHistory = new BatteryStatsHistory(Parcel.obtain(), storeDirectory, 0, 10000,
                 mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
                 mMonotonicClock, null, null);
-        mPowerStatsAggregator = new PowerStatsAggregator(config, mHistory);
+        mPowerStatsAggregator = new PowerStatsAggregator(config);
 
-        mCpuStatsArrayLayout = new CpuPowerStatsLayout();
-        mCpuStatsArrayLayout.addDeviceSectionCpuTimeByScalingStep(1);
-        mCpuStatsArrayLayout.addDeviceSectionCpuTimeByCluster(1);
-        mCpuStatsArrayLayout.addDeviceSectionUsageDuration();
-        mCpuStatsArrayLayout.addDeviceSectionPowerEstimate();
-        mCpuStatsArrayLayout.addUidSectionCpuTimeByPowerBracket(new int[]{0});
-        mCpuStatsArrayLayout.addUidSectionPowerEstimate();
+        mCpuStatsArrayLayout = new CpuPowerStatsLayout(0, 1, new int[]{0});
         PersistableBundle extras = new PersistableBundle();
         mCpuStatsArrayLayout.toExtras(extras);
 
         mPowerStatsDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU,
                 mCpuStatsArrayLayout.getDeviceStatsArrayLength(),
                 null, 0, mCpuStatsArrayLayout.getUidStatsArrayLength(), extras);
+
+        mPowerAttributor = new MultiStatePowerAttributor(mock(Context.class), mPowerStatsStore,
+                mock(PowerProfile.class), mock(CpuScalingPolicies.class),
+                mock(PowerStatsUidResolver.class));
     }
 
     @Test
@@ -329,10 +336,12 @@
                 includeScreenStateData, includesPowerStateData);
     }
 
-    private @NonNull BatteryUsageStats exportToBatteryUsageStats(AggregatedPowerStats aps,
+    private @NonNull BatteryUsageStats exportToBatteryUsageStats(
+            AggregatedPowerStats aps,
             boolean includeProcessStateData, boolean includeScreenStateData,
             boolean includesPowerStateData) {
-        PowerStatsExporter exporter = new PowerStatsExporter(mPowerStatsStore,
+        PowerStatsExporter
+                exporter = new PowerStatsExporter(mPowerStatsStore,
                 mPowerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
 
         BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0], false,
@@ -508,8 +517,8 @@
         mCpuStatsArrayLayout.setUidTimeByPowerBracket(uidStats2, 0, 2469);
         mHistory.recordPowerStats(3000, 3000, powerStats);
 
-        mPowerStatsAggregator.aggregatePowerStats(0, 3500,
-                stats -> mPowerStatsStore.storeAggregatedPowerStats(stats));
+        mPowerStatsAggregator.aggregatePowerStats(mHistory, 0, 3500,
+                stats -> mPowerAttributor.storeAggregatedPowerStats(stats));
 
         mHistory.recordProcessStateChange(4000, 4000, APP_UID1,
                 BatteryConsumer.PROCESS_STATE_BACKGROUND);
@@ -525,9 +534,8 @@
         mEnergyConsumerPowerStatsLayout.setUidConsumedEnergy(customUidStats, 0, 360_000);
         mHistory.recordPowerStats(6010, 6010, customPowerStats);
 
-        mPowerStatsAggregator.aggregatePowerStats(3500, 6500, stats -> {
-            mPowerStatsStore.storeAggregatedPowerStats(stats);
-        });
+        mPowerStatsAggregator.aggregatePowerStats(mHistory, 3500, 6500,
+                stats -> mPowerAttributor.storeAggregatedPowerStats(stats));
 
         mHistory.recordStateStartEvent(7000, 7000, BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG);
         mHistory.recordProcessStateChange(7000, 7000, APP_UID1,
@@ -548,7 +556,8 @@
         recordBatteryHistory();
         PowerStatsExporter exporter = new PowerStatsExporter(mPowerStatsStore,
                 mPowerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
-        exporter.exportAggregatedPowerStats(builder, monotonicStartTime, monotonicEndTime);
+        exporter.exportAggregatedPowerStats(builder, mHistory, monotonicStartTime,
+                monotonicEndTime);
     }
 
     private void assertAggregatedPowerEstimate(String message, BatteryUsageStats bus, int scope,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsProcessorTest.java
similarity index 94%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsProcessorTest.java
index 02e446a..13e0d9d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsProcessorTest.java
@@ -13,11 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java
similarity index 83%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java
index 94f5662..1852165 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/ScreenPowerStatsProcessorTest.java
@@ -14,22 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
@@ -40,7 +40,12 @@
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.PowerStatsCollector;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.ScreenPowerStatsCollector;
 import com.android.server.power.stats.ScreenPowerStatsCollector.Injector;
+import com.android.server.power.stats.format.ScreenPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -48,7 +53,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
 public class ScreenPowerStatsProcessorTest {
@@ -106,11 +110,6 @@
         }
 
         @Override
-        public IntSupplier getVoltageSupplier() {
-            return () -> VOLTAGE_MV;
-        }
-
-        @Override
         public int getDisplayCount() {
             return 2;
         }
@@ -124,6 +123,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
     }
 
     @Test
@@ -177,8 +177,8 @@
         if (energyConsumer) {
             when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
                     .thenReturn(new int[]{77});
-            when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
-                    .thenReturn(new long[]{10_000});
+            when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{77}))
+                    .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(10_000)});
         } else {
             when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
                     .thenReturn(new int[0]);
@@ -200,8 +200,8 @@
         if (energyConsumer) {
             // 400 mAh represented as microWattSeconds
             long energyUws = 400L * 3600 * VOLTAGE_MV;
-            when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
-                    .thenReturn(new long[]{10_000 + energyUws});
+            when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{77}))
+                    .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(10_000 + energyUws)});
         }
 
         when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0))
@@ -243,16 +243,14 @@
 
     private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
             Supplier<PowerStatsProcessor> processorSupplier) {
-        AggregatedPowerStatsConfig.PowerComponent config =
-                new AggregatedPowerStatsConfig.PowerComponent(
-                        BatteryConsumer.POWER_COMPONENT_SCREEN)
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SCREEN)
                         .trackDeviceStates(STATE_POWER, STATE_SCREEN)
                         .trackUidStates(STATE_POWER, STATE_SCREEN)
                         .setProcessorSupplier(processorSupplier);
 
-        PowerComponentAggregatedPowerStats aggregatedStats =
-                new PowerComponentAggregatedPowerStats(
-                        new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+        PowerComponentAggregatedPowerStats aggregatedStats = new AggregatedPowerStats(config)
+                .getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_SCREEN);
 
         aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
         aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
@@ -260,7 +258,14 @@
         return aggregatedStats;
     }
 
-    private void assertDevicePowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats,
+    private EnergyConsumerResult mockEnergyConsumer(long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        return ecr;
+    }
+
+    private void assertDevicePowerEstimate(
+            PowerComponentAggregatedPowerStats aggregatedStats,
             int powerState, int screenState, double expectedScreenPowerEstimate,
             double expectedDozePowerEstimate) {
         PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor();
@@ -273,7 +278,8 @@
                 .of(expectedDozePowerEstimate);
     }
 
-    private void assertUidPowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats, int uid,
+    private void assertUidPowerEstimate(
+            PowerComponentAggregatedPowerStats aggregatedStats, int uid,
             int powerState, int screenState, double expectedScreenPowerEstimate) {
         PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor();
         ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(descriptor);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java
similarity index 91%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java
index 687d70b..d972604 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/SensorPowerStatsProcessorTest.java
@@ -14,19 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -43,6 +43,8 @@
 
 import com.android.internal.os.MonotonicClock;
 import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.format.SensorPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -109,8 +111,7 @@
         stats.finish(10000);
 
         PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
-        SensorPowerStatsLayout statsLayout = new SensorPowerStatsLayout();
-        statsLayout.fromExtras(descriptor.extras);
+        SensorPowerStatsLayout statsLayout = new SensorPowerStatsLayout(descriptor);
 
         String dump = stats.toString();
         assertThat(dump).contains(" step_counter: ");
@@ -207,10 +208,8 @@
                         AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
                 .setProcessorSupplier(processorSupplier);
 
-        AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
-        PowerComponentAggregatedPowerStats powerComponentStats =
-                aggregatedPowerStats.getPowerComponentStats(
-                        BatteryConsumer.POWER_COMPONENT_SENSORS);
+        PowerComponentAggregatedPowerStats powerComponentStats = new AggregatedPowerStats(config)
+                .getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_SENSORS);
         powerComponentStats.start(0);
 
         powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java
similarity index 91%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java
index 11c09bc..baf468e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.power.stats;
+package com.android.server.power.stats.processor;
 
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.METERED_NO;
@@ -23,12 +23,12 @@
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
 
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
-import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.processor.AggregatedPowerStatsConfig.STATE_SCREEN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,6 +41,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
 import android.net.NetworkStats;
 import android.net.wifi.WifiManager;
@@ -53,6 +54,13 @@
 
 import com.android.internal.os.Clock;
 import com.android.internal.os.PowerProfile;
+import com.android.server.power.stats.BatteryUsageStatsRule;
+import com.android.server.power.stats.MockBatteryStatsImpl;
+import com.android.server.power.stats.PowerStatsCollector;
+import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.WifiPowerStatsCollector;
+import com.android.server.power.stats.WifiPowerStatsCollector.WifiStatsRetriever;
+import com.android.server.power.stats.format.WifiPowerStatsLayout;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -61,7 +69,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
-import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 
 public class WifiPowerStatsProcessorTest {
@@ -109,8 +116,7 @@
     private final SparseArray<ScanTimes> mScanTimes = new SparseArray<>();
     private long mWifiActiveDuration;
 
-    private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever =
-            new WifiPowerStatsCollector.WifiStatsRetriever() {
+    private final WifiStatsRetriever mWifiStatsRetriever = new WifiStatsRetriever() {
         @Override
         public void retrieveWifiScanTimes(Callback callback) {
             for (int i = 0; i < mScanTimes.size(); i++) {
@@ -159,11 +165,6 @@
                 }
 
                 @Override
-                public IntSupplier getVoltageSupplier() {
-                    return () -> VOLTAGE_MV;
-                }
-
-                @Override
                 public Supplier<NetworkStats> getWifiNetworkStatsSupplier() {
                     return mNetworkStatsSupplier;
                 }
@@ -174,7 +175,7 @@
                 }
 
                 @Override
-                public WifiPowerStatsCollector.WifiStatsRetriever getWifiStatsRetriever() {
+                public WifiStatsRetriever getWifiStatsRetriever() {
                     return mWifiStatsRetriever;
                 }
             };
@@ -308,6 +309,7 @@
         when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(true);
 
         // PowerStats hardware is available
+        when(mConsumedEnergyRetriever.getVoltageMv()).thenReturn(VOLTAGE_MV);
         when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
                 .thenReturn(new int[] {WIFI_ENERGY_CONSUMER_ID});
 
@@ -321,9 +323,9 @@
         mockWifiActivityEnergyInfo(new WifiActivityEnergyInfo(0L,
                 WifiActivityEnergyInfo.STACK_STATE_INVALID, 0L, 0L, 0L, 0L));
 
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+        when(mConsumedEnergyRetriever.getConsumedEnergy(
                 new int[]{WIFI_ENERGY_CONSUMER_ID}))
-                .thenReturn(new long[]{0});
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(0)});
 
         aggregatedStats.start(0);
 
@@ -354,8 +356,8 @@
 
         // 10 mAh represented as microWattSeconds
         long energyUws = 10 * 3600 * VOLTAGE_MV;
-        when(mConsumedEnergyRetriever.getConsumedEnergyUws(
-                new int[]{WIFI_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
+        when(mConsumedEnergyRetriever.getConsumedEnergy(new int[]{WIFI_ENERGY_CONSUMER_ID}))
+                .thenReturn(new EnergyConsumerResult[]{mockEnergyConsumer(energyUws)});
 
         aggregatedStats.addPowerStats(collector.collectStats(), 10_000);
 
@@ -525,15 +527,14 @@
 
     private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
             Supplier<PowerStatsProcessor> processorSupplier) {
-        AggregatedPowerStatsConfig.PowerComponent config =
-                new AggregatedPowerStatsConfig.PowerComponent(BatteryConsumer.POWER_COMPONENT_WIFI)
-                        .trackDeviceStates(STATE_POWER, STATE_SCREEN)
-                        .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
-                        .setProcessorSupplier(processorSupplier);
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_WIFI)
+                .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+                .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+                .setProcessorSupplier(processorSupplier);
 
-        PowerComponentAggregatedPowerStats aggregatedStats =
-                new PowerComponentAggregatedPowerStats(
-                        new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+        PowerComponentAggregatedPowerStats aggregatedStats = new AggregatedPowerStats(config)
+                .getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_WIFI);
 
         aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
         aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
@@ -547,6 +548,12 @@
         return states;
     }
 
+    private EnergyConsumerResult mockEnergyConsumer(long energyUWs) {
+        EnergyConsumerResult ecr = new EnergyConsumerResult();
+        ecr.energyUWs = energyUWs;
+        return ecr;
+    }
+
     private void mockWifiActivityEnergyInfo(WifiActivityEnergyInfo waei) {
         doAnswer(invocation -> {
             WifiManager.OnWifiActivityEnergyInfoListener
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 09f81f7..a862c43 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -678,3 +678,176 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.input"],
 }
+
+test_module_config {
+    name: "FrameworksServicesTests_server_job",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.job"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_tare",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.tare"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_usage",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.usage"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_om",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.om"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_accessibility",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.accessibility"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_binarytransparencyservicetest",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.BinaryTransparencyServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_pinnerservicetest",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.PinnerServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_am",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.am."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_hdmi",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.hdmi"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_logcat",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.logcat"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_net_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.net."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_policy_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.policy."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_policy",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.policy."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_power",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.power"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_power_hint",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.power.hint"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_location_contexthub_Postsubmit",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.location.contexthub."],
+    include_annotations: ["android.platform.test.annotations.Postsubmit"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_server_input",
+    base: "FrameworksServicesTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.input"],
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index c8cbbb5..2e6c93c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -102,6 +102,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityWindowAttributes;
 import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IUserInitializationCompleteCallback;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
@@ -137,6 +138,7 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 import org.mockito.internal.util.reflection.FieldReader;
 import org.mockito.internal.util.reflection.FieldSetter;
 import org.mockito.stubbing.Answer;
@@ -209,6 +211,7 @@
     @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController;
     @Mock private ProxyManager mProxyManager;
     @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+    @Spy private IUserInitializationCompleteCallback mUserInitializationCompleteCallback;
     @Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor;
     private IAccessibilityManager mA11yManagerServiceOnDevice;
     private AccessibilityServiceConnection mAccessibilityServiceConnection;
@@ -2042,6 +2045,36 @@
                 .isEqualTo(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
     }
 
+    @Test
+    public void registerUserInitializationCompleteCallback_isRegistered() {
+        mA11yms.mUserInitializationCompleteCallbacks.clear();
+
+        mA11yms.registerUserInitializationCompleteCallback(mUserInitializationCompleteCallback);
+
+        assertThat(mA11yms.mUserInitializationCompleteCallbacks).containsExactly(
+                mUserInitializationCompleteCallback);
+    }
+
+    @Test
+    public void unregisterUserInitializationCompleteCallback_isUnregistered() {
+        mA11yms.mUserInitializationCompleteCallbacks.clear();
+        mA11yms.mUserInitializationCompleteCallbacks.add(mUserInitializationCompleteCallback);
+
+        mA11yms.unregisterUserInitializationCompleteCallback(mUserInitializationCompleteCallback);
+
+        assertThat(mA11yms.mUserInitializationCompleteCallbacks).isEmpty();
+    }
+
+    @Test
+    public void switchUser_callsUserInitializationCompleteCallback() throws RemoteException {
+        mA11yms.mUserInitializationCompleteCallbacks.add(mUserInitializationCompleteCallback);
+
+        mA11yms.switchUser(UserHandle.MIN_SECONDARY_USER_ID);
+
+        verify(mUserInitializationCompleteCallback).onUserInitializationComplete(
+                UserHandle.MIN_SECONDARY_USER_ID);
+    }
+
     private Set<String> readStringsFromSetting(String setting) {
         final Set<String> result = new ArraySet<>();
         mA11yms.readColonDelimitedSettingToSet(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index 9083a1e..1904145 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -1319,7 +1319,6 @@
 
         final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
         when(window.getWindowInfo()).thenReturn(windowInfo);
-        when(window.ignoreRecentsAnimationForAccessibility()).thenReturn(false);
         when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
         when(window.isTouchable()).thenReturn(true);
         when(window.getType()).thenReturn(windowInfo.type);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
index 8753b25..019ccf9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt
@@ -48,6 +48,7 @@
 import java.util.LinkedList
 import java.util.Queue
 import android.util.ArraySet
+import android.view.InputDevice
 
 /**
  * Tests for {@link MouseKeysInterceptor}
@@ -68,6 +69,8 @@
     }
 
     private lateinit var mouseKeysInterceptor: MouseKeysInterceptor
+    private lateinit var inputDevice: InputDevice
+
     private val clock = OffsettableClock()
     private val testLooper = TestLooper { clock.now() }
     private val nextInterceptor = TrackingInterceptor()
@@ -98,6 +101,10 @@
         testSession = InputManagerGlobal.createTestSession(iInputManager)
         mockInputManager = InputManager(context)
 
+        inputDevice = createInputDevice(DEVICE_ID)
+        Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID))
+                .thenReturn(inputDevice)
+
         Mockito.`when`(mockVirtualDeviceManagerInternal.getDeviceIdsForUid(Mockito.anyInt()))
             .thenReturn(ArraySet(setOf(DEVICE_ID)))
         LocalServices.removeServiceForTest(VirtualDeviceManagerInternal::class.java)
@@ -115,7 +122,8 @@
         Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
         Mockito.`when`(mockAms.traceManager).thenReturn(mockTraceManager)
 
-        mouseKeysInterceptor = MouseKeysInterceptor(mockAms, testLooper.looper, DISPLAY_ID)
+        mouseKeysInterceptor = MouseKeysInterceptor(mockAms, mockInputManager,
+                testLooper.looper, DISPLAY_ID)
         mouseKeysInterceptor.next = nextInterceptor
     }
 
@@ -281,6 +289,17 @@
         }
     }
 
+    private fun createInputDevice(
+            deviceId: Int,
+            generation: Int = -1
+    ): InputDevice =
+            InputDevice.Builder()
+                    .setId(deviceId)
+                    .setName("Device $deviceId")
+                    .setDescriptor("descriptor $deviceId")
+                    .setGeneration(generation)
+                    .build()
+
     private class TrackingInterceptor : BaseEventStreamTransformation() {
         val events: Queue<KeyEvent> = LinkedList()
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
index 4ec2fb9..cdaeade 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -89,14 +89,15 @@
                         AccessibilityCheckResult.AccessibilityCheckResultType.NOT_RUN, null, 5,
                         null);
 
-        Set<AndroidAccessibilityCheckerResult> results =
-                AccessibilityCheckerUtils.processResults(
-                        mockNodeInfo,
-                        List.of(result1, result2, result3, result4),
-                        null,
+
+        AndroidAccessibilityCheckerResult.Builder resultBuilder =
+                AccessibilityCheckerUtils.getCommonResultBuilder(mockNodeInfo, null,
                         mMockPackageManager,
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
                                 TEST_A11Y_SERVICE_CLASS_NAME));
+        Set<AndroidAccessibilityCheckerResult> results =
+                AccessibilityCheckerUtils.processResults(mockNodeInfo,
+                        List.of(result1, result2, result3, result4), resultBuilder);
 
         assertThat(results).containsExactly(
                 createResult("TargetNode", "",
@@ -128,14 +129,14 @@
                         TouchTargetSizeCheck.class,
                         AccessibilityCheckResult.AccessibilityCheckResultType.ERROR, null, 2, null);
 
-        Set<AndroidAccessibilityCheckerResult> results =
-                AccessibilityCheckerUtils.processResults(
-                        mockNodeInfo,
-                        List.of(result1, result2),
-                        null,
+        AndroidAccessibilityCheckerResult.Builder resultBuilder =
+                AccessibilityCheckerUtils.getCommonResultBuilder(mockNodeInfo, null,
                         mMockPackageManager,
                         new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
                                 TEST_A11Y_SERVICE_CLASS_NAME));
+        Set<AndroidAccessibilityCheckerResult> results =
+                AccessibilityCheckerUtils.processResults(mockNodeInfo,
+                        List.of(result1, result2), resultBuilder);
 
         assertThat(results).isEmpty();
     }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 957ee06..598d3a3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -23,6 +23,8 @@
 import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
+import static android.view.MotionEvent.TOOL_TYPE_FINGER;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -1414,6 +1416,49 @@
     }
 
     @Test
+    public void testSynthesizedGestureEventsDoNotMoveMagnifierViewport() {
+        final EventCaptor eventCaptor = new EventCaptor();
+        mMgh.setNext(eventCaptor);
+
+        float centerX =
+                (INITIAL_MAGNIFICATION_BOUNDS.left + INITIAL_MAGNIFICATION_BOUNDS.width()) / 2.0f;
+        float centerY =
+                (INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.height()) / 2.0f;
+        float scale = 5.6f; // value is unimportant but unique among tests to increase coverage.
+        mFullScreenMagnificationController.setScaleAndCenter(
+                DISPLAY_0, centerX, centerY, scale, /* animate= */ false, 1);
+        centerX = mFullScreenMagnificationController.getCenterX(DISPLAY_0);
+        centerY = mFullScreenMagnificationController.getCenterY(DISPLAY_0);
+
+        // Second finger down on trackpad starts a synthesized two-finger swipe with source
+        // mouse.
+        MotionEvent downEvent = motionEvent(centerX, centerY, ACTION_DOWN,
+                TOOL_TYPE_FINGER, CLASSIFICATION_TWO_FINGER_SWIPE);
+        send(downEvent, InputDevice.SOURCE_MOUSE);
+        fastForward(20);
+
+        // Two-finger swipe creates a synthesized move event, and shouldn't impact magnifier
+        // viewport.
+        MotionEvent moveEvent = motionEvent(centerX - 42, centerY - 42, ACTION_MOVE,
+                TOOL_TYPE_FINGER, CLASSIFICATION_TWO_FINGER_SWIPE);
+        send(moveEvent, InputDevice.SOURCE_MOUSE);
+        fastForward(20);
+
+        assertThat(mFullScreenMagnificationController.getCenterX(DISPLAY_0)).isEqualTo(centerX);
+        assertThat(mFullScreenMagnificationController.getCenterY(DISPLAY_0)).isEqualTo(centerY);
+
+        // The events were not consumed by magnifier.
+        assertThat(eventCaptor.mEvents.size()).isEqualTo(2);
+        assertThat(eventCaptor.mEvents.get(0).getSource()).isEqualTo(InputDevice.SOURCE_MOUSE);
+        assertThat(eventCaptor.mEvents.get(1).getSource()).isEqualTo(InputDevice.SOURCE_MOUSE);
+
+        final List<Integer> expectedActions = new ArrayList();
+        expectedActions.add(Integer.valueOf(ACTION_DOWN));
+        expectedActions.add(Integer.valueOf(ACTION_MOVE));
+        assertActionsInOrder(eventCaptor.mEvents, expectedActions);
+    }
+
+    @Test
     @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
     public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() {
         runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE);
@@ -2130,6 +2175,30 @@
         return MotionEvent.obtain(mLastDownTime, mClock.now(), action, x, y, 0);
     }
 
+    private MotionEvent motionEvent(float x, float y, int action, int toolType,
+            int classification) {
+        // Create a generic motion event to populate the parameters.
+        MotionEvent event = motionEvent(x, y, action);
+        int pointerCount = event.getPointerCount();
+        MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount];
+        MotionEvent.PointerProperties[] properties =
+                new MotionEvent.PointerProperties[pointerCount];
+        for (int i = 0; i < pointerCount; i++) {
+            properties[i] = new MotionEvent.PointerProperties();
+            event.getPointerProperties(i, properties[i]);
+            properties[i].toolType = toolType;
+            coords[i] = new MotionEvent.PointerCoords();
+            event.getPointerCoords(i, coords[i]);
+        }
+        // Apply the custom classification.
+        return MotionEvent.obtain(event.getDownTime(), event.getEventTime(), action,
+                /*pointerCount=*/1, properties, coords,
+                event.getMetaState(), event.getButtonState(),
+                event.getXPrecision(), event.getYPrecision(), event.getDeviceId(),
+                event.getEdgeFlags(), event.getSource(), event.getDisplayId(), event.getFlags(),
+                classification);
+    }
+
     private MotionEvent mouseEvent(float x, float y, int action) {
         return fromMouse(motionEvent(x, y, action));
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 1db46bf..a25621a 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -55,6 +55,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
@@ -89,6 +90,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IRemoteCallback;
+import android.os.IpcDataCache;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManagerInternal;
@@ -145,7 +147,6 @@
  */
 @SmallTest
 @Presubmit
-
 public class UserControllerTest {
     // Use big enough user id to avoid picking up already active user id.
     private static final int TEST_USER_ID = 100;
@@ -197,6 +198,9 @@
     @Before
     public void setUp() throws Exception {
         runWithDexmakerShareClassLoader(() -> {
+            // Disable binder caches in this process.
+            IpcDataCache.disableForTestMode();
+
             mInjector = spy(new TestInjector(getInstrumentation().getTargetContext()));
             doNothing().when(mInjector).clearAllLockedTasks(anyString());
             doNothing().when(mInjector).startHomeActivity(anyInt(), anyString());
@@ -593,6 +597,7 @@
     @Test
     public void testScheduleStopOfBackgroundUser_switch() {
         mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+        assumeFalse(UserManager.isVisibleBackgroundUsersEnabled());
 
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
@@ -642,6 +647,7 @@
     @Test
     public void testScheduleStopOfBackgroundUser_startInBackground() throws Exception {
         mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+        assumeFalse(UserManager.isVisibleBackgroundUsersEnabled());
 
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
@@ -681,6 +687,7 @@
     @Test
     public void testScheduleStopOfBackgroundUser_rescheduleWhenGuest() throws Exception {
         mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+        assumeFalse(UserManager.isVisibleBackgroundUsersEnabled());
 
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
@@ -736,6 +743,7 @@
     @Test
     public void testScheduleStopOfBackgroundUser_rescheduleIfAlarm() throws Exception {
         mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+        assumeFalse(UserManager.isVisibleBackgroundUsersEnabled());
 
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index e45ab31..beed0a3d 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -111,6 +111,9 @@
     private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
             AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
 
+    /** Choose a default stream volume value which does not depend on min/max. */
+    private static final int DEFAULT_STREAM_VOLUME = 2;
+
     @Rule
     public final MockitoRule mockito = MockitoJUnit.rule();
 
@@ -144,6 +147,8 @@
 
     private TestLooper mTestLooper;
 
+    private boolean mIsAutomotive;
+
     public static final int[] BASIC_VOLUME_BEHAVIORS = {
             AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE,
             AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL,
@@ -232,9 +237,10 @@
                 || packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
         final boolean isSingleVolume = mContext.getResources().getBoolean(
                 Resources.getSystem().getIdentifier("config_single_volume", "bool", "android"));
-        final boolean automotiveHardened = mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE) && autoPublicVolumeApiHardening();
-        assumeFalse("Skipping test for fixed, TV, single volume and auto devices",
+        mIsAutomotive = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE);
+        final boolean automotiveHardened = mIsAutomotive && autoPublicVolumeApiHardening();
+        assumeFalse("Skipping test for fixed, TV, single volume and auto hardened devices",
                 useFixedVolume || isTelevision || isSingleVolume || automotiveHardened);
 
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
@@ -249,15 +255,14 @@
                 {STREAM_MUSIC, STREAM_NOTIFICATION, STREAM_RING, STREAM_ALARM, STREAM_SYSTEM,
                         STREAM_VOICE_CALL, STREAM_ACCESSIBILITY};
         for (int streamType : usedStreamTypes) {
-            final int streamVolume = (mAm.getStreamMinVolume(streamType) + mAm.getStreamMaxVolume(
-                    streamType)) / 2;
-
-            mAudioService.setStreamVolume(streamType, streamVolume, /*flags=*/0,
+            mAudioService.setStreamVolume(streamType, DEFAULT_STREAM_VOLUME, /*flags=*/0,
                     mContext.getOpPackageName());
         }
 
-        mAudioService.setRingerModeInternal(RINGER_MODE_NORMAL, mContext.getOpPackageName());
-        mAudioService.setRingerModeExternal(RINGER_MODE_NORMAL, mContext.getOpPackageName());
+        if (!mIsAutomotive) {
+            mAudioService.setRingerModeInternal(RINGER_MODE_NORMAL, mContext.getOpPackageName());
+            mAudioService.setRingerModeExternal(RINGER_MODE_NORMAL, mContext.getOpPackageName());
+        }
     }
 
     private AudioVolumeGroup getStreamTypeVolumeGroup(int streamType) {
@@ -297,6 +302,7 @@
 
     @Test
     public void setStreamRingVolume0_setsRingerModeVibrate() throws Exception {
+        assumeFalse("Skipping ringer mode test on automotive", mIsAutomotive);
         mAudioService.setStreamVolume(STREAM_RING, 0, /*flags=*/0,
                 mContext.getOpPackageName());
         mTestLooper.dispatchAll();
@@ -462,6 +468,7 @@
 
     @Test
     public void flagAllowRingerModes_onSystemStreams_changesMode() throws Exception {
+        assumeFalse("Skipping ringer mode test on automotive", mIsAutomotive);
         mAudioService.setStreamVolume(STREAM_SYSTEM,
                 mAudioService.getStreamMinVolume(STREAM_SYSTEM), /*flags=*/0,
                 mContext.getOpPackageName());
@@ -476,6 +483,7 @@
 
     @Test
     public void flagAllowRingerModesAbsent_onNonSystemStreams_noModeChange() throws Exception {
+        assumeFalse("Skipping ringer mode test on automotive", mIsAutomotive);
         mAudioService.setStreamVolume(STREAM_MUSIC,
                 mAudioService.getStreamMinVolume(STREAM_MUSIC), /*flags=*/0,
                 mContext.getOpPackageName());
@@ -544,17 +552,23 @@
         mAudioService.setDeviceVolume(volMin, usbDevice, mContext.getOpPackageName());
         mTestLooper.dispatchAll();
 
-        assertEquals(mAudioService.getDeviceVolume(volMin, usbDevice,
-                mContext.getOpPackageName()), volMin);
+        if (!mIsAutomotive) {
+            // there is a min/max index mismatch in automotive
+            assertEquals(mAudioService.getDeviceVolume(volMin, usbDevice,
+                    mContext.getOpPackageName()), volMin);
+        }
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+                eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
 
         mAudioService.setDeviceVolume(volMid, usbDevice, mContext.getOpPackageName());
         mTestLooper.dispatchAll();
-        assertEquals(mAudioService.getDeviceVolume(volMid, usbDevice,
-                mContext.getOpPackageName()), volMid);
+        if (!mIsAutomotive) {
+            // there is a min/max index mismatch in automotive
+            assertEquals(mAudioService.getDeviceVolume(volMid, usbDevice,
+                    mContext.getOpPackageName()), volMid);
+        }
         verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
-                STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+                eq(STREAM_MUSIC), anyInt(), eq(AudioSystem.DEVICE_OUT_USB_DEVICE));
     }
 
     @Test
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/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 4c3a233..760d38e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -264,6 +264,22 @@
         assertThat(preAuthInfo.eligibleSensors).hasSize(0);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testMandatoryBiometricsNegativeButtonText_whenSet()
+            throws Exception {
+        when(mTrustManager.isInSignificantPlace()).thenReturn(false);
+
+        final BiometricSensor sensor = getFaceSensor();
+        final PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+        promptInfo.setNegativeButtonText(TEST_PACKAGE_NAME);
+        final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+        assertThat(promptInfo.getNegativeButtonText()).isEqualTo(TEST_PACKAGE_NAME);
+    }
+
     private BiometricSensor getFingerprintSensor() {
         BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
                 TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
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/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 5a78d9e..1a593dd 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -37,7 +37,6 @@
 
 import android.app.WindowConfiguration;
 import android.companion.virtual.IVirtualDeviceIntentInterceptor;
-import android.companion.virtual.VirtualDeviceManager;
 import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Context;
@@ -94,15 +93,9 @@
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Mock
-    private VirtualDeviceManager.ActivityListener mActivityListener;
-    @Mock
-    private GenericWindowPolicyController.IntentListenerCallback mIntentListenerCallback;
-    @Mock
-    private GenericWindowPolicyController.ActivityBlockedCallback mActivityBlockedCallback;
+    private GenericWindowPolicyController.ActivityListener mActivityListener;
     @Mock
     private GenericWindowPolicyController.RunningAppsChangedListener mRunningAppsChangedListener;
-    @Mock
-    private GenericWindowPolicyController.SecureWindowCallback mSecureWindowCallback;
 
     @Before
     public void setUp() throws Exception {
@@ -669,14 +662,14 @@
                 /* targetDisplayCategory */ null);
 
         // register interceptor and intercept intent
-        when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(true);
+        when(mActivityListener.shouldInterceptIntent(any(Intent.class))).thenReturn(true);
         assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /* isNewTask= */false,
                 /* isResultExpected = */ false, /* intentSender= */ null))
                 .isFalse();
 
         // unregister interceptor and launch activity
-        when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
+        when(mActivityListener.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
         assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /* isNewTask= */false,
                 /* isResultExpected = */ false, /* intentSender= */ null))
@@ -696,13 +689,12 @@
                 /* targetDisplayCategory */ null);
 
         // register interceptor with different filter
-        when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
+        when(mActivityListener.shouldInterceptIntent(any(Intent.class))).thenReturn(false);
         assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /* isNewTask= */false,
                 /* isResultExpected = */ false, /* intentSender= */ null))
                 .isTrue();
-        verify(mIntentListenerCallback, timeout(TIMEOUT_MILLIS))
-                .shouldInterceptIntent(any(Intent.class));
+        verify(mActivityListener, timeout(TIMEOUT_MILLIS)).shouldInterceptIntent(any(Intent.class));
     }
 
     @Test
@@ -723,8 +715,8 @@
                 /* isResultExpected = */ true, /* intentSender= */ () -> intentSender))
                 .isFalse();
 
-        verify(mActivityBlockedCallback, timeout(TIMEOUT_MILLIS))
-                .onActivityBlocked(DISPLAY_ID, activityInfo, /* intentSender= */ null);
+        verify(mActivityListener, timeout(TIMEOUT_MILLIS))
+                .onActivityLaunchBlocked(DISPLAY_ID, activityInfo, /* intentSender= */ null);
     }
 
     @Test
@@ -761,10 +753,10 @@
 
         assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue();
 
-        verify(mSecureWindowCallback, after(TIMEOUT_MILLIS).never())
-                .onSecureWindowShown(DISPLAY_ID, activityInfo.applicationInfo.uid);
-        verify(mActivityBlockedCallback, never())
-                .onActivityBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
+        verify(mActivityListener, after(TIMEOUT_MILLIS).never())
+                .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+        verify(mActivityListener, never())
+                .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
     }
 
     @Test
@@ -780,10 +772,10 @@
 
         assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, FLAG_SECURE, 0)).isTrue();
 
-        verify(mSecureWindowCallback, timeout(TIMEOUT_MILLIS)).onSecureWindowShown(DISPLAY_ID,
-                activityInfo.applicationInfo.uid);
-        verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never())
-                .onActivityBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
+        verify(mActivityListener, timeout(TIMEOUT_MILLIS))
+                .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+        verify(mActivityListener, after(TIMEOUT_MILLIS).never())
+                .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
     }
 
     @Test
@@ -800,10 +792,10 @@
         assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0,
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)).isTrue();
 
-        verify(mSecureWindowCallback, after(TIMEOUT_MILLIS).never())
-                .onSecureWindowShown(DISPLAY_ID, activityInfo.applicationInfo.uid);
-        verify(mActivityBlockedCallback, never())
-                .onActivityBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
+        verify(mActivityListener, after(TIMEOUT_MILLIS).never())
+                .onSecureWindowShown(eq(DISPLAY_ID), eq(activityInfo));
+        verify(mActivityListener, never())
+                .onActivityLaunchBlocked(eq(DISPLAY_ID), eq(activityInfo), any());
     }
 
     @Test
@@ -835,9 +827,6 @@
                 /* crossTaskNavigationAllowedByDefault= */ true,
                 /* crossTaskNavigationExemptions= */ new ArraySet<>(),
                 /* activityListener= */ mActivityListener,
-                /* activityBlockedCallback= */ mActivityBlockedCallback,
-                /* secureWindowCallback= */ mSecureWindowCallback,
-                /* intentListenerCallback= */ mIntentListenerCallback,
                 /* displayCategories= */ new ArraySet<>(),
                 /* showTasksInHostDeviceRecents= */ true,
                 /* customHomeComponent= */ null);
@@ -855,9 +844,6 @@
                 /* crossTaskNavigationAllowedByDefault= */ true,
                 /* crossTaskNavigationExemptions= */ new ArraySet<>(),
                 /* activityListener= */ mActivityListener,
-                /* activityBlockedCallback= */ mActivityBlockedCallback,
-                /* secureWindowCallback= */ mSecureWindowCallback,
-                /* intentListenerCallback= */ mIntentListenerCallback,
                 /* displayCategories= */ new ArraySet<>(),
                 /* showTasksInHostDeviceRecents= */ true,
                 /* customHomeComponent= */ null);
@@ -876,9 +862,6 @@
                 /* crossTaskNavigationAllowedByDefault= */ true,
                 /* crossTaskNavigationExemptions= */ new ArraySet<>(),
                 /* activityListener= */ mActivityListener,
-                /* activityBlockedCallback= */ mActivityBlockedCallback,
-                /* secureWindowCallback= */ null,
-                /* intentListenerCallback= */ mIntentListenerCallback,
                 /* displayCategories= */ new ArraySet<>(),
                 /* showTasksInHostDeviceRecents= */ true,
                 /* customHomeComponent= */ homeComponent);
@@ -897,9 +880,6 @@
                 /* crossTaskNavigationAllowedByDefault= */ true,
                 /* crossTaskNavigationExemptions= */ new ArraySet<>(),
                 /* activityListener= */ mActivityListener,
-                /* activityBlockedCallback= */ mActivityBlockedCallback,
-                /* secureWindowCallback= */ null,
-                /* intentListenerCallback= */ mIntentListenerCallback,
                 /* displayCategories= */ new ArraySet<>(),
                 /* showTasksInHostDeviceRecents= */ true,
                 /* customHomeComponent= */ null);
@@ -918,9 +898,6 @@
                 /* crossTaskNavigationAllowedByDefault= */ true,
                 /* crossTaskNavigationExemptions= */ new ArraySet<>(),
                 /* activityListener= */ mActivityListener,
-                /* activityBlockedCallback= */ mActivityBlockedCallback,
-                /* secureWindowCallback= */ null,
-                /* intentListenerCallback= */ mIntentListenerCallback,
                 /* displayCategories= */ new ArraySet<>(),
                 /* showTasksInHostDeviceRecents= */ true,
                 /* customHomeComponent= */ null);
@@ -939,9 +916,6 @@
                 /* crossTaskNavigationAllowedByDefault= */ true,
                 /* crossTaskNavigationExemptions= */ new ArraySet<>(),
                 /* activityListener= */ mActivityListener,
-                /* activityBlockedCallback= */ mActivityBlockedCallback,
-                /* secureWindowCallback= */ null,
-                /* intentListenerCallback= */ mIntentListenerCallback,
                 /* displayCategories= */ Collections.singleton(displayCategory),
                 /* showTasksInHostDeviceRecents= */ true,
                 /* customHomeComponent= */ null);
@@ -960,9 +934,6 @@
                 /* crossTaskNavigationAllowedByDefault= */ true,
                 /* crossTaskNavigationExemptions= */ Collections.singleton(blockedComponent),
                 /* activityListener= */ mActivityListener,
-                /* activityBlockedCallback= */ mActivityBlockedCallback,
-                /* secureWindowCallback= */ null,
-                /* intentListenerCallback= */ mIntentListenerCallback,
                 /* displayCategories= */ new ArraySet<>(),
                 /* showTasksInHostDeviceRecents= */ true,
                 /* customHomeComponent= */ null);
@@ -981,9 +952,6 @@
                 /* crossTaskNavigationAllowedByDefault= */ false,
                 /* crossTaskNavigationExemptions= */ Collections.singleton(allowedComponent),
                 /* activityListener= */ mActivityListener,
-                /* activityBlockedCallback= */ mActivityBlockedCallback,
-                /* secureWindowCallback= */ null,
-                /* intentListenerCallback= */ mIntentListenerCallback,
                 /* displayCategories= */ new ArraySet<>(),
                 /* showTasksInHostDeviceRecents= */ true,
                 /* customHomeComponent= */ null);
@@ -1029,9 +997,9 @@
         assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
                 isNewTask, /* isResultExpected= */ false, () -> intentSender)).isTrue();
 
-        verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never())
-                .onActivityBlocked(fromDisplay, activityInfo, intentSender);
-        verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
+        verify(mActivityListener, after(TIMEOUT_MILLIS).never())
+                .onActivityLaunchBlocked(fromDisplay, activityInfo, intentSender);
+        verify(mActivityListener, never()).shouldInterceptIntent(any(Intent.class));
     }
 
     private void assertActivityIsBlocked(GenericWindowPolicyController gwpc,
@@ -1046,9 +1014,9 @@
         assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
                 isNewTask, /* isResultExpected= */ false, () -> intentSender)).isFalse();
 
-        verify(mActivityBlockedCallback, timeout(TIMEOUT_MILLIS))
-                .onActivityBlocked(fromDisplay, activityInfo, intentSender);
-        verify(mIntentListenerCallback, after(TIMEOUT_MILLIS).never())
+        verify(mActivityListener, timeout(TIMEOUT_MILLIS))
+                .onActivityLaunchBlocked(fromDisplay, activityInfo, intentSender);
+        verify(mActivityListener, after(TIMEOUT_MILLIS).never())
                 .shouldInterceptIntent(any(Intent.class));
     }
 
@@ -1060,8 +1028,8 @@
                 /* isResultExpected= */ false, () -> intentSender))
                 .isFalse();
 
-        verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never())
-                .onActivityBlocked(eq(fromDisplay), eq(activityInfo), any());
-        verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
+        verify(mActivityListener, after(TIMEOUT_MILLIS).never())
+                .onActivityLaunchBlocked(eq(fromDisplay), eq(activityInfo), any());
+        verify(mActivityListener, never()).shouldInterceptIntent(any(Intent.class));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 405929a..51c2ad1 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -87,9 +87,6 @@
                         /* crossTaskNavigationAllowedByDefault= */ true,
                         /* crossTaskNavigationExemptions= */ new ArraySet<>(),
                         /* activityListener= */ null,
-                        /* activityBlockedCallback= */ null,
-                        /* secureWindowCallback= */ null,
-                        /* intentListenerCallback= */ null,
                         /* displayCategories= */ new ArraySet<>(),
                         /* showTasksInHostDeviceRecents= */ true,
                         /* customHomeComponent= */ null);
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/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
index b2fd8aa..161b18c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
@@ -165,7 +165,7 @@
 
         assertTrue(resultContains(
                 callShellCommand("reset-throttling", "--user", "10"),
-                "User 10 is not running or locked"));
+                "User (with userId=10) is not running or locked"));
 
         mRunningUsers.put(USER_10, true);
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 7912156..e652df5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -34,6 +34,7 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.IpcDataCache;
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -100,6 +101,9 @@
 
     @Before
     public void setUp() throws Exception {
+        // Disable binder caches in this process.
+        IpcDataCache.disableForTestMode();
+
         mOriginalCurrentUserId = ActivityManager.getCurrentUser();
         mUserManager = UserManager.get(mContext);
         mActivityManager = mContext.getSystemService(ActivityManager.class);
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/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 963b27e..bf58443 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -38,6 +38,7 @@
 import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
 import android.media.tv.tunerresourcemanager.TunerLnbRequest;
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -69,6 +70,61 @@
     private TunerResourceManagerService mTunerResourceManagerService;
     private boolean mIsForeground;
 
+    private final class TunerClient extends IResourcesReclaimListener.Stub {
+        int[] mClientId;
+        ClientProfile mProfile;
+        boolean mReclaimed;
+
+        TunerClient() {
+            mClientId = new int[1];
+            mClientId[0] = TunerResourceManagerService.INVALID_CLIENT_ID;
+        }
+
+        public void register(String sessionId, int useCase) {
+            ResourceClientProfile profile = new ResourceClientProfile();
+            profile.tvInputSessionId = sessionId;
+            profile.useCase = useCase;
+            mTunerResourceManagerService.registerClientProfileInternal(
+                    profile, this, mClientId);
+            assertThat(mClientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+            mProfile = mTunerResourceManagerService.getClientProfile(mClientId[0]);
+        }
+
+        public void register(String sessionId, int useCase, int priority, int niceValue) {
+            register(sessionId, useCase);
+            mTunerResourceManagerService.updateClientPriorityInternal(
+                    mClientId[0], priority, niceValue);
+        }
+
+        public void register(String sessionId, int useCase, int priority) {
+            register(sessionId, useCase, priority, 0);
+        }
+
+        public void unregister() {
+            mTunerResourceManagerService.unregisterClientProfileInternal(mClientId[0]);
+            mClientId[0] = TunerResourceManagerService.INVALID_CLIENT_ID;
+            mReclaimed = false;
+        }
+
+        public int getId() {
+            return mClientId[0];
+        }
+
+        public ClientProfile getProfile() {
+            return mProfile;
+        }
+
+        @Override
+        public void onReclaimResources() {
+            mTunerResourceManagerService.clearAllResourcesAndClientMapping(mProfile);
+            mReclaimed = true;
+        }
+
+        public boolean isReclaimed() {
+            return mReclaimed;
+        }
+    }
+
     private static final class TestResourcesReclaimListener extends IResourcesReclaimListener.Stub {
         boolean mReclaimed;
 
@@ -247,13 +303,11 @@
     }
 
     @Test
-    public void requestFrontendTest_NoFrontendWithGiveTypeAvailable() {
-        ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId = new int[1];
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile, null /*listener*/, clientId);
-        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+    public void requestFrontendTest_NoFrontendWithGiveTypeAvailable() throws RemoteException {
+        // Register clients
+        TunerClient client0 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         // Init frontend resources.
         TunerFrontendInfo[] infos = new TunerFrontendInfo[1];
@@ -262,21 +316,20 @@
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
         TunerFrontendRequest request =
-                tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+                tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isFalse();
         assertThat(frontendHandle[0]).isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
+        client0.unregister();
     }
 
     @Test
-    public void requestFrontendTest_FrontendWithNoExclusiveGroupAvailable() {
-        ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId = new int[1];
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile, null /*listener*/, clientId);
-        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+    public void requestFrontendTest_FrontendWithNoExclusiveGroupAvailable() throws RemoteException {
+        // Register clients
+        TunerClient client0 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         // Init frontend resources.
         TunerFrontendInfo[] infos = new TunerFrontendInfo[3];
@@ -295,27 +348,23 @@
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
         TunerFrontendRequest request =
-                tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+                tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(frontendHandle[0]).isEqualTo(0);
+        client0.unregister();
     }
 
     @Test
-    public void requestFrontendTest_FrontendWithExclusiveGroupAvailable() {
-        ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId0 = new int[1];
-        int[] clientId1 = new int[1];
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile0, null /*listener*/, clientId0);
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile1, null /*listener*/, clientId1);
-        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+    public void requestFrontendTest_FrontendWithExclusiveGroupAvailable() throws RemoteException {
+        // Register clients
+        TunerClient client0 = new TunerClient();
+        TunerClient client1 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        client1.register("1" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         // Init frontend resources.
         TunerFrontendInfo[] infos = new TunerFrontendInfo[3];
@@ -335,13 +384,13 @@
 
         int[] frontendHandle = new int[1];
         TunerFrontendRequest request =
-                tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+                tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
 
         request =
-                tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+                tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(frontendHandle[0]).isEqualTo(infos[1].handle);
@@ -349,31 +398,20 @@
                 .isTrue();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[2].handle).isInUse())
                 .isTrue();
+        client0.unregister();
+        client1.unregister();
     }
 
     @Test
-    public void requestFrontendTest_NoFrontendAvailable_RequestWithLowerPriority() {
+    public void requestFrontendTest_NoFrontendAvailable_RequestWithLowerPriority()
+            throws RemoteException {
         // Register clients
-        ResourceClientProfile[] profiles = new ResourceClientProfile[2];
-        profiles[0] = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        profiles[1] = resourceClientProfile("1" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientPriorities = {100, 50};
-        int[] clientId0 = new int[1];
-        int[] clientId1 = new int[1];
-        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[0], listener, clientId0);
-        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId0[0], clientPriorities[0], 0/*niceValue*/);
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[1], new TestResourcesReclaimListener(), clientId1);
-        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId1[0], clientPriorities[1], 0/*niceValue*/);
+        TunerClient client0 = new TunerClient();
+        TunerClient client1 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+        client1.register("1" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 50);
 
         // Init frontend resources.
         TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -384,46 +422,36 @@
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
         TunerFrontendRequest request =
-                tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+                tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
 
         request =
-                tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+                tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isFalse();
-        assertThat(listener.isReclaimed()).isFalse();
+        assertThat(client0.isReclaimed()).isFalse();
 
         request =
-                tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
+                tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBS);
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isFalse();
-        assertThat(listener.isReclaimed()).isFalse();
+        assertThat(client0.isReclaimed()).isFalse();
+        client0.unregister();
+        client1.unregister();
     }
 
     @Test
-    public void requestFrontendTest_NoFrontendAvailable_RequestWithHigherPriority() {
+    public void requestFrontendTest_NoFrontendAvailable_RequestWithHigherPriority()
+            throws RemoteException {
         // Register clients
-        ResourceClientProfile[] profiles = new ResourceClientProfile[2];
-        profiles[0] = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        profiles[1] = resourceClientProfile("1" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientPriorities = {100, 500};
-        int[] clientId0 = new int[1];
-        int[] clientId1 = new int[1];
-        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[0], listener, clientId0);
-        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId0[0], clientPriorities[0], 0/*niceValue*/);
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[1], new TestResourcesReclaimListener(), clientId1);
-        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId1[0], clientPriorities[1], 0/*niceValue*/);
+        TunerClient client0 = new TunerClient();
+        TunerClient client1 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+        client1.register("1" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500);
 
         // Init frontend resources.
         TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -434,17 +462,16 @@
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
         TunerFrontendRequest request =
-                tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+                tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .getInUseFrontendHandles()).isEqualTo(new HashSet<Integer>(Arrays.asList(
-                        infos[0].handle, infos[1].handle)));
+        assertThat(client0.getProfile().getInUseFrontendHandles())
+                .isEqualTo(new HashSet<Integer>(Arrays.asList(infos[0].handle, infos[1].handle)));
 
         request =
-                tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
+                tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBS);
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(frontendHandle[0]).isEqualTo(infos[1].handle);
@@ -453,22 +480,20 @@
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
-                .getOwnerClientId()).isEqualTo(clientId1[0]);
+                .getOwnerClientId()).isEqualTo(client1.getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
-                .getOwnerClientId()).isEqualTo(clientId1[0]);
-        assertThat(listener.isReclaimed()).isTrue();
+                .getOwnerClientId()).isEqualTo(client1.getId());
+        assertThat(client0.isReclaimed()).isTrue();
+        client0.unregister();
+        client1.unregister();
     }
 
     @Test
-    public void releaseFrontendTest_UnderTheSameExclusiveGroup() {
+    public void releaseFrontendTest_UnderTheSameExclusiveGroup() throws RemoteException {
         // Register clients
-        ResourceClientProfile[] profiles = new ResourceClientProfile[1];
-        profiles[0] = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId = new int[1];
-        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-        mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
-        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        TunerClient client0 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         // Init frontend resources.
         TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -479,7 +504,7 @@
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
         TunerFrontendRequest request =
-                tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+                tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
@@ -488,43 +513,29 @@
                 .getFrontendResource(infos[1].handle).isInUse()).isTrue();
 
         // Release frontend
-        mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService
-                .getFrontendResource(frontendHandle[0]), clientId[0]);
+        mTunerResourceManagerService.releaseFrontendInternal(frontendHandle[0], client0.getId());
         assertThat(mTunerResourceManagerService
                 .getFrontendResource(frontendHandle[0]).isInUse()).isFalse();
         assertThat(mTunerResourceManagerService
                 .getFrontendResource(infos[1].handle).isInUse()).isFalse();
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(clientId[0]).getInUseFrontendHandles().size()).isEqualTo(0);
+        assertThat(client0.getProfile().getInUseFrontendHandles().size()).isEqualTo(0);
+        client0.unregister();
     }
 
     @Test
-    public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() {
+    public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() throws RemoteException {
         // Register clients
-        ResourceClientProfile[] profiles = new ResourceClientProfile[2];
-        profiles[0] = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        profiles[1] = resourceClientProfile("1" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientPriorities = {100, 500};
-        int[] clientId0 = new int[1];
-        int[] clientId1 = new int[1];
-        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[0], listener, clientId0);
-        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId0[0], clientPriorities[0], 0/*niceValue*/);
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[1], new TestResourcesReclaimListener(), clientId1);
-        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId1[0], clientPriorities[1], 0/*niceValue*/);
+        TunerClient client0 = new TunerClient();
+        TunerClient client1 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+        client1.register("1" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500);
 
         // Init cas resources.
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
 
-        CasSessionRequest request = casSessionRequest(clientId0[0], 1 /*casSystemId*/);
+        CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/);
         int[] casSessionHandle = new int[1];
         // Request for 2 cas sessions.
         assertThat(mTunerResourceManagerService
@@ -533,54 +544,45 @@
                 .requestCasSessionInternal(request, casSessionHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
                 .isEqualTo(1);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .getInUseCasSystemId()).isEqualTo(1);
+        assertThat(client0.getProfile().getInUseCasSystemId())
+                .isEqualTo(1);
         assertThat(mTunerResourceManagerService.getCasResource(1)
-                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0])));
+                .getOwnerClientIds()).isEqualTo(
+                        new HashSet<Integer>(Arrays.asList(client0.getId())));
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
 
-        request = casSessionRequest(clientId1[0], 1);
+        request = casSessionRequest(client1.getId(), 1);
         assertThat(mTunerResourceManagerService
                 .requestCasSessionInternal(request, casSessionHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
                 .isEqualTo(1);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0])
-                .getInUseCasSystemId()).isEqualTo(1);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+        assertThat(client1.getProfile().getInUseCasSystemId()).isEqualTo(1);
+        assertThat(client0.getProfile().getInUseCasSystemId())
+                .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
         assertThat(mTunerResourceManagerService.getCasResource(1)
-                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0])));
+                .getOwnerClientIds()).isEqualTo(
+                        new HashSet<Integer>(Arrays.asList(client1.getId())));
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
-        assertThat(listener.isReclaimed()).isTrue();
+        assertThat(client0.isReclaimed()).isTrue();
+        client0.unregister();
+        client1.unregister();
     }
 
     @Test
-    public void requestCiCamTest_NoCiCamAvailable_RequestWithHigherPriority() {
+    public void requestCiCamTest_NoCiCamAvailable_RequestWithHigherPriority()
+            throws RemoteException {
         // Register clients
-        ResourceClientProfile[] profiles = new ResourceClientProfile[2];
-        profiles[0] = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        profiles[1] = resourceClientProfile("1" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientPriorities = {100, 500};
-        int[] clientId0 = new int[1];
-        int[] clientId1 = new int[1];
-        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[0], listener, clientId0);
-        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId0[0], clientPriorities[0], 0/*niceValue*/);
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[1], new TestResourcesReclaimListener(), clientId1);
-        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId1[0], clientPriorities[1], 0/*niceValue*/);
+        TunerClient client0 = new TunerClient();
+        TunerClient client1 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+        client1.register("1" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500);
 
         // Init cicam/cas resources.
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
 
-        TunerCiCamRequest request = tunerCiCamRequest(clientId0[0], 1 /*ciCamId*/);
+        TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/);
         int[] ciCamHandle = new int[1];
         // Request for 2 ciCam sessions.
         assertThat(mTunerResourceManagerService
@@ -589,139 +591,125 @@
                 .requestCiCamInternal(request, ciCamHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
                 .isEqualTo(1);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .getInUseCiCamId()).isEqualTo(1);
+        assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1);
         assertThat(mTunerResourceManagerService.getCiCamResource(1)
-                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0])));
+                .getOwnerClientIds()).isEqualTo(
+                        new HashSet<Integer>(Arrays.asList(client0.getId())));
         assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue();
 
-        request = tunerCiCamRequest(clientId1[0], 1);
+        request = tunerCiCamRequest(client1.getId(), 1);
         assertThat(mTunerResourceManagerService
                 .requestCiCamInternal(request, ciCamHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
                 .isEqualTo(1);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0])
-                .getInUseCiCamId()).isEqualTo(1);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .getInUseCiCamId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+        assertThat(client1.getProfile().getInUseCiCamId()).isEqualTo(1);
+        assertThat(client0.getProfile().getInUseCiCamId())
+                .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
         assertThat(mTunerResourceManagerService.getCiCamResource(1)
-                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0])));
-        assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse();
-        assertThat(listener.isReclaimed()).isTrue();
+                .getOwnerClientIds()).isEqualTo(
+                        new HashSet<Integer>(Arrays.asList(client1.getId())));
+        assertThat(mTunerResourceManagerService
+                .getCiCamResource(1).isFullyUsed()).isFalse();
+        assertThat(client0.isReclaimed()).isTrue();
+        client0.unregister();
+        client1.unregister();
     }
 
     @Test
-    public void releaseCasTest() {
+    public void releaseCasTest() throws RemoteException {
         // Register clients
-        ResourceClientProfile[] profiles = new ResourceClientProfile[1];
-        profiles[0] = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId = new int[1];
-        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-        mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
-        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        TunerClient client0 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         // Init cas resources.
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
 
-        CasSessionRequest request = casSessionRequest(clientId[0], 1 /*casSystemId*/);
+        CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/);
         int[] casSessionHandle = new int[1];
         // Request for 1 cas sessions.
         assertThat(mTunerResourceManagerService
                 .requestCasSessionInternal(request, casSessionHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
                 .isEqualTo(1);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
-                .getInUseCasSystemId()).isEqualTo(1);
+        assertThat(client0.getProfile().getInUseCasSystemId()).isEqualTo(1);
         assertThat(mTunerResourceManagerService.getCasResource(1)
-                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0])));
+                .getOwnerClientIds()).isEqualTo(
+                        new HashSet<Integer>(Arrays.asList(client0.getId())));
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
 
         // Release cas
         mTunerResourceManagerService.releaseCasSessionInternal(mTunerResourceManagerService
-                .getCasResource(1), clientId[0]);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
-                .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+                .getCasResource(1), client0.getId());
+        assertThat(client0.getProfile().getInUseCasSystemId())
+                .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
         assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
         assertThat(mTunerResourceManagerService.getCasResource(1)
                 .getOwnerClientIds()).isEmpty();
+        client0.unregister();
     }
 
     @Test
-    public void releaseCiCamTest() {
+    public void releaseCiCamTest() throws RemoteException {
         // Register clients
-        ResourceClientProfile[] profiles = new ResourceClientProfile[1];
-        profiles[0] = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId = new int[1];
-        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-        mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
-        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        TunerClient client0 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         // Init cas resources.
         mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
 
-        TunerCiCamRequest request = tunerCiCamRequest(clientId[0], 1 /*ciCamId*/);
+        TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/);
         int[] ciCamHandle = new int[1];
         // Request for 1 ciCam sessions.
         assertThat(mTunerResourceManagerService
                 .requestCiCamInternal(request, ciCamHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0]))
                 .isEqualTo(1);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
-                .getInUseCiCamId()).isEqualTo(1);
+        assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1);
         assertThat(mTunerResourceManagerService.getCiCamResource(1)
-                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0])));
-        assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse();
+                .getOwnerClientIds()).isEqualTo(
+                        new HashSet<Integer>(Arrays.asList(client0.getId())));
+        assertThat(mTunerResourceManagerService
+                .getCiCamResource(1).isFullyUsed()).isFalse();
 
         // Release ciCam
         mTunerResourceManagerService.releaseCiCamInternal(mTunerResourceManagerService
-                .getCiCamResource(1), clientId[0]);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
-                .getInUseCiCamId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
-        assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse();
+                .getCiCamResource(1), client0.getId());
+        assertThat(client0.getProfile().getInUseCiCamId())
+                .isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+        assertThat(mTunerResourceManagerService
+                .getCiCamResource(1).isFullyUsed()).isFalse();
         assertThat(mTunerResourceManagerService.getCiCamResource(1)
                 .getOwnerClientIds()).isEmpty();
+        client0.unregister();
     }
 
     @Test
-    public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() {
+    public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() throws RemoteException {
         // Register clients
-        ResourceClientProfile[] profiles = new ResourceClientProfile[2];
-        profiles[0] = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        profiles[1] = resourceClientProfile("1" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientPriorities = {100, 500};
-        int[] clientId0 = new int[1];
-        int[] clientId1 = new int[1];
-        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[0], listener, clientId0);
-        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId0[0], clientPriorities[0], 0/*niceValue*/);
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profiles[1], new TestResourcesReclaimListener(), clientId1);
-        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                clientId1[0], clientPriorities[1], 0/*niceValue*/);
+        TunerClient client0 = new TunerClient();
+        TunerClient client1 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100);
+        client1.register("1" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500);
 
         // Init lnb resources.
         int[] lnbHandles = {1};
         mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
 
         TunerLnbRequest request = new TunerLnbRequest();
-        request.clientId = clientId0[0];
+        request.clientId = client0.getId();
         int[] lnbHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestLnbInternal(request, lnbHandle)).isTrue();
         assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]);
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]).getInUseLnbHandles())
+        assertThat(client0.getProfile().getInUseLnbHandles())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(lnbHandles[0])));
 
         request = new TunerLnbRequest();
-        request.clientId = clientId1[0];
+        request.clientId = client1.getId();
 
         assertThat(mTunerResourceManagerService
                 .requestLnbInternal(request, lnbHandle)).isTrue();
@@ -729,29 +717,26 @@
         assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0])
                 .isInUse()).isTrue();
         assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0])
-                .getOwnerClientId()).isEqualTo(clientId1[0]);
-        assertThat(listener.isReclaimed()).isTrue();
-        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
-                .getInUseLnbHandles().size()).isEqualTo(0);
+                .getOwnerClientId()).isEqualTo(client1.getId());
+        assertThat(client0.isReclaimed()).isTrue();
+        assertThat(client0.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
+        client0.unregister();
+        client1.unregister();
     }
 
     @Test
-    public void releaseLnbTest() {
+    public void releaseLnbTest() throws RemoteException {
         // Register clients
-        ResourceClientProfile[] profiles = new ResourceClientProfile[1];
-        profiles[0] = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId = new int[1];
-        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
-        mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
-        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        TunerClient client0 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         // Init lnb resources.
         int[] lnbHandles = {0};
         mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles);
 
         TunerLnbRequest request = new TunerLnbRequest();
-        request.clientId = clientId[0];
+        request.clientId = client0.getId();
         int[] lnbHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestLnbInternal(request, lnbHandle)).isTrue();
@@ -762,19 +747,16 @@
                 .getLnbResource(lnbHandle[0]));
         assertThat(mTunerResourceManagerService
                 .getLnbResource(lnbHandle[0]).isInUse()).isFalse();
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(clientId[0]).getInUseLnbHandles().size()).isEqualTo(0);
+        assertThat(client0.getProfile().getInUseLnbHandles().size()).isEqualTo(0);
+        client0.unregister();
     }
 
     @Test
-    public void unregisterClientTest_usingFrontend() {
-        // Register client
-        ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId = new int[1];
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile, null /*listener*/, clientId);
-        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+    public void unregisterClientTest_usingFrontend() throws RemoteException {
+        // Register clients
+        TunerClient client0 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         // Init frontend resources.
         TunerFrontendInfo[] infos = new TunerFrontendInfo[2];
@@ -785,7 +767,7 @@
         mTunerResourceManagerService.setFrontendInfoListInternal(infos);
 
         TunerFrontendRequest request =
-                tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
+                tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestFrontendInternal(request, frontendHandle)).isTrue();
@@ -796,26 +778,20 @@
                 .isInUse()).isTrue();
 
         // Unregister client when using frontend
-        mTunerResourceManagerService.unregisterClientProfileInternal(clientId[0]);
+        client0.unregister();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
                 .isInUse()).isFalse();
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
                 .isInUse()).isFalse();
-        assertThat(mTunerResourceManagerService.checkClientExists(clientId[0])).isFalse();
-
+        assertThat(mTunerResourceManagerService.checkClientExists(client0.getId())).isFalse();
     }
 
     @Test
-    public void requestDemuxTest() {
-        // Register client
-        ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId0 = new int[1];
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile0, null /*listener*/, clientId0);
-        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+    public void requestDemuxTest() throws RemoteException {
+        // Register clients
+        TunerClient client0 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         TunerDemuxInfo[] infos = new TunerDemuxInfo[3];
         infos[0] = tunerDemuxInfo(0 /* handle */, Filter.TYPE_TS | Filter.TYPE_IP);
@@ -825,7 +801,7 @@
 
         int[] demuxHandle0 = new int[1];
         // first with undefined type (should be the first one with least # of caps)
-        TunerDemuxRequest request = tunerDemuxRequest(clientId0[0], Filter.TYPE_UNDEFINED);
+        TunerDemuxRequest request = tunerDemuxRequest(client0.getId(), Filter.TYPE_UNDEFINED);
         assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0))
                 .isTrue();
         assertThat(demuxHandle0[0]).isEqualTo(1);
@@ -846,16 +822,16 @@
         assertThat(demuxHandle0[0]).isEqualTo(2);
 
         // request for another TS
-        int[] clientId1 = new int[1];
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile1, null /*listener*/, clientId1);
-        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        TunerClient client1 = new TunerClient();
+        client1.register("1" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+
         int[] demuxHandle1 = new int[1];
-        TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_TS);
+        TunerDemuxRequest request1 = tunerDemuxRequest(client1.getId(), Filter.TYPE_TS);
         assertThat(mTunerResourceManagerService.requestDemuxInternal(request1, demuxHandle1))
                 .isTrue();
         assertThat(demuxHandle1[0]).isEqualTo(0);
-        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(demuxHandle1[0]))
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(client1.getId()))
                 .isEqualTo(0);
 
         // release demuxes
@@ -863,33 +839,23 @@
         mTunerResourceManagerService.releaseDemuxInternal(dr);
         dr = mTunerResourceManagerService.getDemuxResource(demuxHandle1[0]);
         mTunerResourceManagerService.releaseDemuxInternal(dr);
+
+        client0.unregister();
+        client1.unregister();
     }
 
     @Test
-    public void requestDemuxTest_ResourceReclaim() {
+    public void requestDemuxTest_ResourceReclaim() throws RemoteException {
         // Register clients
-        ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
-        ResourceClientProfile profile2 = resourceClientProfile("2" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
-        int[] clientId0 = new int[1];
-        int[] clientId1 = new int[1];
-        int[] clientId2 = new int[1];
-        TestResourcesReclaimListener listener0 = new TestResourcesReclaimListener();
-        TestResourcesReclaimListener listener1 = new TestResourcesReclaimListener();
-        TestResourcesReclaimListener listener2 = new TestResourcesReclaimListener();
-
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile0, listener0, clientId0);
-        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile1, listener1, clientId1);
-        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile2, listener2, clientId1);
-        assertThat(clientId2[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        TunerClient client0 = new TunerClient();
+        TunerClient client1 = new TunerClient();
+        TunerClient client2 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        client1.register("1" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
+        client2.register("2" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN);
 
         // Init demux resources.
         TunerDemuxInfo[] infos = new TunerDemuxInfo[2];
@@ -897,66 +863,67 @@
         infos[1] = tunerDemuxInfo(1 /*handle*/, Filter.TYPE_TS);
         mTunerResourceManagerService.setDemuxInfoListInternal(infos);
 
-        // let clientId0(prio:100) request for IP - should succeed
-        TunerDemuxRequest request0 = tunerDemuxRequest(clientId0[0], Filter.TYPE_IP);
+        // let client0(prio:100) request for IP - should succeed
+        TunerDemuxRequest request0 = tunerDemuxRequest(client0.getId(), Filter.TYPE_IP);
         int[] demuxHandle0 = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestDemuxInternal(request0, demuxHandle0)).isTrue();
         assertThat(demuxHandle0[0]).isEqualTo(0);
 
-        // let clientId1(prio:50) request for IP - should fail
-        TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_IP);
+        // let client1(prio:50) request for IP - should fail
+        TunerDemuxRequest request1 = tunerDemuxRequest(client1.getId(), Filter.TYPE_IP);
         int[] demuxHandle1 = new int[1];
         demuxHandle1[0] = -1;
         assertThat(mTunerResourceManagerService
                 .requestDemuxInternal(request1, demuxHandle1)).isFalse();
-        assertThat(listener0.isReclaimed()).isFalse();
+        assertThat(client0.isReclaimed()).isFalse();
         assertThat(demuxHandle1[0]).isEqualTo(-1);
 
-        // let clientId1(prio:50) request for TS - should succeed
+        // let client1(prio:50) request for TS - should succeed
         request1.desiredFilterTypes = Filter.TYPE_TS;
         assertThat(mTunerResourceManagerService
                 .requestDemuxInternal(request1, demuxHandle1)).isTrue();
         assertThat(demuxHandle1[0]).isEqualTo(1);
-        assertThat(listener0.isReclaimed()).isFalse();
+        assertThat(client0.isReclaimed()).isFalse();
 
-        // now release demux for the clientId0 (higher priority) and request demux
+        // now release demux for the client0 (higher priority) and request demux
         DemuxResource dr = mTunerResourceManagerService.getDemuxResource(demuxHandle0[0]);
         mTunerResourceManagerService.releaseDemuxInternal(dr);
 
-        // let clientId2(prio:50) request for TS - should succeed
-        TunerDemuxRequest request2 = tunerDemuxRequest(clientId2[0], Filter.TYPE_TS);
+        // let client2(prio:50) request for TS - should succeed
+        TunerDemuxRequest request2 = tunerDemuxRequest(client2.getId(), Filter.TYPE_TS);
         int[] demuxHandle2 = new int[1];
         assertThat(mTunerResourceManagerService
                 .requestDemuxInternal(request2, demuxHandle2)).isTrue();
         assertThat(demuxHandle2[0]).isEqualTo(0);
-        assertThat(listener1.isReclaimed()).isFalse();
+        assertThat(client1.isReclaimed()).isFalse();
 
-        // let clientId0(prio:100) request for TS - should reclaim from clientId2
+        // let client0(prio:100) request for TS - should reclaim from client1
         // , who has the smaller caps
         request0.desiredFilterTypes = Filter.TYPE_TS;
         assertThat(mTunerResourceManagerService
                 .requestDemuxInternal(request0, demuxHandle0)).isTrue();
-        assertThat(listener1.isReclaimed()).isFalse();
-        assertThat(listener2.isReclaimed()).isTrue();
+        assertThat(client1.isReclaimed()).isTrue();
+        assertThat(client2.isReclaimed()).isFalse();
+        client0.unregister();
+        client1.unregister();
+        client2.unregister();
     }
 
     @Test
     public void requestDescramblerTest() {
-        // Register client
-        ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
-        int[] clientId = new int[1];
-        mTunerResourceManagerService.registerClientProfileInternal(
-                profile, null /*listener*/, clientId);
-        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        // Register clients
+        TunerClient client0 = new TunerClient();
+        client0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
 
         int[] desHandle = new int[1];
         TunerDescramblerRequest request = new TunerDescramblerRequest();
-        request.clientId = clientId[0];
+        request.clientId = client0.getId();
         assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle))
                 .isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(desHandle[0])).isEqualTo(0);
+        client0.unregister();
     }
 
     @Test
@@ -978,74 +945,26 @@
     }
 
     @Test
-    public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() {
+    public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() throws RemoteException {
         /**** Register Clients and Set Priority ****/
-
-        // Int array to save the returned client ids
-        int[] ownerClientId0 = new int[1];
-        int[] ownerClientId1 = new int[1];
-        int[] shareClientId0 = new int[1];
-        int[] shareClientId1 = new int[1];
-
-        // Predefined client profiles
-        ResourceClientProfile[] ownerProfiles = new ResourceClientProfile[2];
-        ResourceClientProfile[] shareProfiles = new ResourceClientProfile[2];
-        ownerProfiles[0] = resourceClientProfile(
-                "0" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
-        ownerProfiles[1] = resourceClientProfile(
-                "1" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
-        shareProfiles[0] = resourceClientProfile(
-                "2" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD);
-        shareProfiles[1] = resourceClientProfile(
-                "3" /*sessionId*/,
-                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD);
-
-        // Predefined client reclaim listeners
-        TestResourcesReclaimListener ownerListener0 = new TestResourcesReclaimListener();
-        TestResourcesReclaimListener shareListener0 = new TestResourcesReclaimListener();
-        TestResourcesReclaimListener ownerListener1 = new TestResourcesReclaimListener();
-        TestResourcesReclaimListener shareListener1 = new TestResourcesReclaimListener();
-        // Register clients and validate the returned client ids
-        mTunerResourceManagerService
-                .registerClientProfileInternal(ownerProfiles[0], ownerListener0, ownerClientId0);
-        mTunerResourceManagerService
-                .registerClientProfileInternal(shareProfiles[0], shareListener0, shareClientId0);
-        mTunerResourceManagerService
-                .registerClientProfileInternal(ownerProfiles[1], ownerListener1, ownerClientId1);
-        mTunerResourceManagerService
-                .registerClientProfileInternal(shareProfiles[1], shareListener1, shareClientId1);
-        assertThat(ownerClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        assertThat(shareClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        assertThat(ownerClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
-        assertThat(shareClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        TunerClient ownerClient0 = new TunerClient();
+        TunerClient ownerClient1 = new TunerClient();
+        TunerClient shareClient0 = new TunerClient();
+        TunerClient shareClient1 = new TunerClient();
+        ownerClient0.register("0" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 100);
+        ownerClient1.register("1" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 300);
+        shareClient0.register("2" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD, 200);
+        shareClient1.register("3" /*sessionId*/,
+                        TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD, 400);
 
         mTunerResourceManagerService.updateClientPriorityInternal(
-                ownerClientId0[0],
-                100/*priority*/,
-                0/*niceValue*/);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                shareClientId0[0],
-                200/*priority*/,
-                0/*niceValue*/);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                ownerClientId1[0],
-                300/*priority*/,
-                0/*niceValue*/);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                shareClientId1[0],
-                400/*priority*/,
-                0/*niceValue*/);
-        mTunerResourceManagerService.updateClientPriorityInternal(
-                shareClientId1[0],
+                shareClient1.getId(),
                 -1/*invalid priority*/,
                 0/*niceValue*/);
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(shareClientId1[0])
-                .getPriority())
-                .isEqualTo(400);
+        assertThat(shareClient1.getProfile().getPriority()).isEqualTo(400);
 
         /**** Init Frontend Resources ****/
 
@@ -1072,7 +991,7 @@
         // Predefined frontend request and array to save returned frontend handle
         int[] frontendHandle = new int[1];
         TunerFrontendRequest request = tunerFrontendRequest(
-                ownerClientId0[0] /*clientId*/,
+                ownerClient0.getId() /*clientId*/,
                 FrontendSettings.TYPE_DVBT);
 
         // Request call and validate granted resource and internal mapping
@@ -1080,9 +999,7 @@
                 .requestFrontendInternal(request, frontendHandle))
                 .isTrue();
         assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(ownerClientId0[0])
-                .getInUseFrontendHandles())
+        assertThat(ownerClient0.getProfile().getInUseFrontendHandles())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
                         infos[0].handle,
                         infos[1].handle)));
@@ -1091,11 +1008,11 @@
 
         // Share frontend call and validate the internal mapping
         mTunerResourceManagerService.shareFrontendInternal(
-                shareClientId0[0]/*selfClientId*/,
-                ownerClientId0[0]/*targetClientId*/);
+                shareClient0.getId()/*selfClientId*/,
+                ownerClient0.getId()/*targetClientId*/);
         mTunerResourceManagerService.shareFrontendInternal(
-                shareClientId1[0]/*selfClientId*/,
-                ownerClientId0[0]/*targetClientId*/);
+                shareClient1.getId()/*selfClientId*/,
+                ownerClient0.getId()/*targetClientId*/);
         // Verify fe in use status
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
                 .isInUse()).isTrue();
@@ -1103,31 +1020,24 @@
                 .isInUse()).isTrue();
         // Verify fe owner status
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
-                .getOwnerClientId()).isEqualTo(ownerClientId0[0]);
+                .getOwnerClientId()).isEqualTo(ownerClient0.getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
-                .getOwnerClientId()).isEqualTo(ownerClientId0[0]);
+                .getOwnerClientId()).isEqualTo(ownerClient0.getId());
         // Verify share fe client status in the primary owner client
-        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
-                .getShareFeClientIds())
+        assertThat(ownerClient0.getProfile().getShareFeClientIds())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
-                        shareClientId0[0],
-                        shareClientId1[0])));
+                        shareClient0.getId(),
+                        shareClient1.getId())));
         // Verify in use frontend list in all the primary owner and share owner clients
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(ownerClientId0[0])
-                .getInUseFrontendHandles())
+        assertThat(ownerClient0.getProfile().getInUseFrontendHandles())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
                         infos[0].handle,
                         infos[1].handle)));
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(shareClientId0[0])
-                .getInUseFrontendHandles())
+        assertThat(shareClient0.getProfile().getInUseFrontendHandles())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
                         infos[0].handle,
                         infos[1].handle)));
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(shareClientId1[0])
-                .getInUseFrontendHandles())
+        assertThat(shareClient1.getProfile().getInUseFrontendHandles())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
                         infos[0].handle,
                         infos[1].handle)));
@@ -1135,21 +1045,17 @@
         /**** Remove Frontend Share Owner ****/
 
         // Unregister the second share fe client
-        mTunerResourceManagerService.unregisterClientProfileInternal(shareClientId1[0]);
+        shareClient1.unregister();
 
         // Validate the internal mapping
-        assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0])
-                .getShareFeClientIds())
+        assertThat(ownerClient0.getProfile().getShareFeClientIds())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
-                        shareClientId0[0])));
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(ownerClientId0[0])
-                .getInUseFrontendHandles())
+                        shareClient0.getId())));
+        assertThat(ownerClient0.getProfile().getInUseFrontendHandles())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
                         infos[0].handle,
                         infos[1].handle)));
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(shareClientId0[0])
+        assertThat(shareClient0.getProfile()
                 .getInUseFrontendHandles())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
                         infos[0].handle,
@@ -1159,7 +1065,7 @@
 
         // Predefined second frontend request
         request = tunerFrontendRequest(
-                ownerClientId1[0] /*clientId*/,
+                ownerClient1.getId() /*clientId*/,
                 FrontendSettings.TYPE_DVBT);
 
         // Second request call
@@ -1170,43 +1076,35 @@
         // Validate granted resource and internal mapping
         assertThat(frontendHandle[0]).isEqualTo(infos[0].handle);
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
-                .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
+                .getOwnerClientId()).isEqualTo(ownerClient1.getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
-                .getOwnerClientId()).isEqualTo(ownerClientId1[0]);
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(ownerClientId1[0])
-                .getInUseFrontendHandles())
+                .getOwnerClientId()).isEqualTo(ownerClient1.getId());
+        assertThat(ownerClient1.getProfile().getInUseFrontendHandles())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
                         infos[0].handle,
                         infos[1].handle)));
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(ownerClientId0[0])
-                .getInUseFrontendHandles()
+        assertThat(ownerClient0.getProfile().getInUseFrontendHandles()
                 .isEmpty())
                 .isTrue();
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(shareClientId0[0])
-                .getInUseFrontendHandles()
+        assertThat(shareClient0.getProfile().getInUseFrontendHandles()
                 .isEmpty())
                 .isTrue();
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(ownerClientId0[0])
-                .getShareFeClientIds()
+        assertThat(ownerClient0.getProfile().getShareFeClientIds()
                 .isEmpty())
                 .isTrue();
-        assertThat(ownerListener0.isReclaimed()).isTrue();
-        assertThat(shareListener0.isReclaimed()).isTrue();
+        assertThat(ownerClient0.isReclaimed()).isTrue();
+        assertThat(shareClient0.isReclaimed()).isTrue();
 
         /**** Release Frontend Resource From Primary Owner ****/
 
         // Reshare the frontend
         mTunerResourceManagerService.shareFrontendInternal(
-                shareClientId0[0]/*selfClientId*/,
-                ownerClientId1[0]/*targetClientId*/);
+                shareClient0.getId()/*selfClientId*/,
+                ownerClient1.getId()/*targetClientId*/);
 
         // Release the frontend resource from the primary owner
-        mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService
-                .getFrontendResource(infos[0].handle), ownerClientId1[0]);
+        mTunerResourceManagerService.releaseFrontendInternal(infos[0].handle,
+                ownerClient1.getId());
 
         // Validate the internal mapping
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
@@ -1214,19 +1112,13 @@
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
                 .isInUse()).isFalse();
         // Verify client status
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(ownerClientId1[0])
-                .getInUseFrontendHandles()
+        assertThat(ownerClient1.getProfile().getInUseFrontendHandles()
                 .isEmpty())
                 .isTrue();
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(shareClientId0[0])
-                .getInUseFrontendHandles()
+        assertThat(shareClient0.getProfile().getInUseFrontendHandles()
                 .isEmpty())
                 .isTrue();
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(ownerClientId1[0])
-                .getShareFeClientIds()
+        assertThat(ownerClient1.getProfile().getShareFeClientIds()
                 .isEmpty())
                 .isTrue();
 
@@ -1234,7 +1126,7 @@
 
         // Predefined Lnb request and handle array
         TunerLnbRequest requestLnb = new TunerLnbRequest();
-        requestLnb.clientId = shareClientId0[0];
+        requestLnb.clientId = shareClient0.getId();
         int[] lnbHandle = new int[1];
 
         // Request for an Lnb
@@ -1247,11 +1139,11 @@
                 .requestFrontendInternal(request, frontendHandle))
                 .isTrue();
         mTunerResourceManagerService.shareFrontendInternal(
-                shareClientId0[0]/*selfClientId*/,
-                ownerClientId1[0]/*targetClientId*/);
+                shareClient0.getId()/*selfClientId*/,
+                ownerClient1.getId()/*targetClientId*/);
 
         // Unregister the primary owner of the shared frontend
-        mTunerResourceManagerService.unregisterClientProfileInternal(ownerClientId1[0]);
+        ownerClient1.unregister();
 
         // Validate the internal mapping
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle)
@@ -1259,16 +1151,15 @@
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle)
                 .isInUse()).isFalse();
         // Verify client status
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(shareClientId0[0])
-                .getInUseFrontendHandles()
+        assertThat(shareClient0.getProfile().getInUseFrontendHandles()
                 .isEmpty())
                 .isTrue();
-        assertThat(mTunerResourceManagerService
-                .getClientProfile(shareClientId0[0])
-                .getInUseLnbHandles())
+        assertThat(shareClient0.getProfile().getInUseLnbHandles())
                 .isEqualTo(new HashSet<Integer>(Arrays.asList(
                         lnbHandles[0])));
+
+        ownerClient0.unregister();
+        shareClient0.unregister();
     }
 
     private TunerFrontendInfo tunerFrontendInfo(
diff --git a/services/tests/timetests/Android.bp b/services/tests/timetests/Android.bp
index 23ab859..05a1433 100644
--- a/services/tests/timetests/Android.bp
+++ b/services/tests/timetests/Android.bp
@@ -25,3 +25,27 @@
     certificate: "platform",
     test_suites: ["device-tests"],
 }
+
+test_module_config {
+    name: "FrameworksTimeServicesTests_time",
+    base: "FrameworksTimeServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: [
+        "com.android.server.timezonedetector.",
+        "com.android.server.timedetector.",
+    ],
+}
+
+test_module_config {
+    name: "FrameworksTimeServicesTests_server_timedetector",
+    base: "FrameworksTimeServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.timedetector."],
+}
+
+test_module_config {
+    name: "FrameworksTimeServicesTests_server_timezonedetector",
+    base: "FrameworksTimeServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.timezonedetector."],
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 7933f7a..4a19973 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -46,6 +46,7 @@
 import android.content.IntentFilter;
 import android.hardware.display.ColorDisplayManager;
 import android.os.PowerManager;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.ZenDeviceEffects;
 import android.testing.TestableContext;
@@ -64,6 +65,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 @RunWith(TestParameterInjector.class)
 public class DefaultDeviceEffectsApplierTest {
 
@@ -89,6 +93,8 @@
 
         mApplier = new DefaultDeviceEffectsApplier(mContext);
         verify(mWallpaperManager).isWallpaperSupported();
+
+        ZenLog.clear();
     }
 
     @Test
@@ -110,6 +116,41 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void apply_logsToZenLog() {
+        when(mPowerManager.isInteractive()).thenReturn(true);
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        ArgumentCaptor<IntentFilter> intentFilterCaptor =
+                ArgumentCaptor.forClass(IntentFilter.class);
+
+        ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+                .setShouldDisplayGrayscale(true)
+                .setShouldUseNightMode(true)
+                .build();
+        mApplier.apply(effects, ORIGIN_APP);
+
+        String zenLog = getZenLog();
+        assertThat(zenLog).contains("apply_device_effect: displayGrayscale -> true");
+        assertThat(zenLog).contains("schedule_device_effect: nightMode -> true");
+        assertThat(zenLog).doesNotContain("apply_device_effect: nightMode");
+
+        verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(),
+                intentFilterCaptor.capture(), anyInt());
+        BroadcastReceiver screenOffReceiver = broadcastReceiverCaptor.getValue();
+        screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
+
+        zenLog = getZenLog();
+        assertThat(zenLog).contains("apply_device_effect: nightMode -> true");
+    }
+
+    private static String getZenLog() {
+        StringWriter zenLogWriter = new StringWriter();
+        ZenLog.dump(new PrintWriter(zenLogWriter), "");
+        return zenLogWriter.toString();
+    }
+
+    @Test
     public void apply_removesEffects() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
 
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 51f64ba..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;
@@ -2204,7 +2206,7 @@
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                 eq(expectedGroupKey_silent), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
-                eq(expectedGroupKey_silent), eq(false));
+                eq(expectedGroupKey_silent), eq(true));
 
         // Check that the alerting section group is removed
         verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2264,13 +2266,15 @@
                 notificationList);
 
         // Check that channel1's notifications are moved to the silent section group
-        expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
-                mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
-                "TEST_CHANNEL_ID1");
-        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
-                eq(expectedGroupKey_silent), anyInt(), eq(expectedSummaryAttr));
-        verify(mCallback, times(AUTOGROUP_AT_COUNT/2 + 1)).addAutoGroup(anyString(),
-                eq(expectedGroupKey_silent), eq(false));
+        // But not enough to auto-group => remove override group key
+        verify(mCallback, never()).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                anyString(), anyInt(), any());
+        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+        for (NotificationRecord record: notificationList) {
+            if (record.getChannel().getId().equals(channel1.getId())) {
+                assertThat(record.getSbn().getOverrideGroupKey()).isNull();
+            }
+        }
 
         // Check that the alerting section group is not removed, only updated
         expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
@@ -2343,7 +2347,7 @@
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                 eq(expectedGroupKey_silent), anyInt(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(numSilentGroupNotifications)).addAutoGroup(anyString(),
-                eq(expectedGroupKey_silent), eq(false));
+                eq(expectedGroupKey_silent), eq(true));
 
         // Check that the alerting section group is removed
         verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
@@ -2353,6 +2357,60 @@
     }
 
     @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    public void testAutogroup_updateChannel_reachedMinAutogroupCount() {
+        final String pkg = "package";
+        final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
+                "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
+        final NotificationChannel channel2 = new NotificationChannel("TEST_CHANNEL_ID2",
+                "TEST_CHANNEL_ID2", IMPORTANCE_LOW);
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        // Post notifications with different channels that would autogroup in different sections
+        NotificationRecord r;
+        // Not enough notifications to autogroup initially
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            if (i % 2 == 0) {
+                r = getNotificationRecord(pkg, i, String.valueOf(i),
+                    UserHandle.SYSTEM, null, false, channel1);
+            } else {
+                r = getNotificationRecord(pkg, i, String.valueOf(i),
+                    UserHandle.SYSTEM, null, false, channel2);
+            }
+            notificationList.add(r);
+            mGroupHelper.onNotificationPosted(r, false);
+        }
+        verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+                anyString(), anyInt(), any());
+        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
+        Mockito.reset(mCallback);
+
+        // Update channel1's importance
+        final String expectedGroupKey_silent = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SilentSection", UserHandle.SYSTEM.getIdentifier());
+        channel1.setImportance(IMPORTANCE_LOW);
+        for (NotificationRecord record: notificationList) {
+            if (record.getChannel().getId().equals(channel1.getId())) {
+                record.updateNotificationChannel(channel1);
+            }
+        }
+        mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
+                notificationList);
+
+        // Check that channel1's notifications are moved to the silent section & autogroup all
+        NotificationAttributes expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
+                mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+                "TEST_CHANNEL_ID1");
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_silent), eq(true));
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_silent), anyInt(), eq(expectedSummaryAttr));
+    }
+
+    @Test
     @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
             Flags.FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
     public void testNoGroup_singletonGroup_underLimit() {
@@ -2519,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();
@@ -2592,8 +2654,6 @@
                 "", false, recsChannel);
         assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
                 "AlertingSection");
-
-        checkNonGroupableNotifications();
     }
 
     @Test
@@ -2638,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..6a1140c 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();
     }
 
@@ -14865,7 +14995,6 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
     public void enqueueNotification_acceptsCorrectToken() throws RemoteException {
         Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentIntent(createPendingIntent("content"))
@@ -14884,7 +15013,6 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
     public void enqueueNotification_acceptsNullToken_andPopulatesIt() throws RemoteException {
         Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentIntent(createPendingIntent("content"))
@@ -14901,7 +15029,6 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
     public void enqueueNotification_directlyThroughRunnable_populatesAllowlistToken() {
         Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentIntent(createPendingIntent("content"))
@@ -14924,7 +15051,6 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
     public void enqueueNotification_rejectsOtherToken() throws RemoteException {
         Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentIntent(createPendingIntent("content"))
@@ -14942,7 +15068,6 @@
     }
 
     @Test
-    @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
     public void enqueueNotification_customParcelingWithFakeInnerToken_hasCorrectTokenInIntents()
             throws RemoteException {
         Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID)
@@ -15148,7 +15273,6 @@
 
     @Test
     @SuppressWarnings("unchecked")
-    @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
     public void getActiveNotifications_doesNotLeakAllowlistToken() throws RemoteException {
         Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setContentIntent(createPendingIntent("content"))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index e70ed5f..f8ff1f4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -788,6 +788,19 @@
     }
 
     @Test
+    public void testRuleXml_invalidInterruptionFilter_readsDefault() throws Exception {
+        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        rule.zenMode = 1979;
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        writeRuleXml(rule, baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ZenModeConfig.ZenRule fromXml = readRuleXml(bais);
+
+        assertThat(fromXml.zenMode).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+    }
+
+    @Test
     public void testZenPolicyXml_allUnset() throws Exception {
         ZenPolicy policy = new ZenPolicy.Builder().build();
 
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 dd2b845..baa633f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.AutomaticZenRule.TYPE_BEDTIME;
 import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.app.Flags.FLAG_MODES_API;
 import static android.app.Flags.FLAG_MODES_UI;
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
@@ -2186,6 +2188,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;
@@ -5538,6 +5614,49 @@
     }
 
     @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void removeAndAddAutomaticZenRule_ifChangingComponent_isAllowedAndDoesNotRestore() {
+        // Start with a rule.
+        mZenModeHelper.mConfig.automaticRules.clear();
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+                .setOwner(new ComponentName("first", "owner"))
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
+                .build();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+                ORIGIN_APP, "add it", CUSTOM_PKG_UID);
+
+        // User customizes it.
+        AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdate, ORIGIN_USER_IN_SYSTEMUI,
+                "userUpdate", SYSTEM_UID);
+
+        // App deletes it. It's preserved for a possible restoration.
+        mZenModeHelper.removeAutomaticZenRule(ruleId, ORIGIN_APP, "delete it",
+                CUSTOM_PKG_UID);
+        assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(0);
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(1);
+
+        // App adds it again, but this time with a different owner!
+        AutomaticZenRule readdingWithDifferentOwner = new AutomaticZenRule.Builder(rule)
+                .setOwner(new ComponentName("second", "owner"))
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .build();
+        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+                readdingWithDifferentOwner, ORIGIN_APP, "add it again", CUSTOM_PKG_UID);
+
+        // Verify that the rule was NOT restored:
+        assertThat(newRuleId).isNotEqualTo(ruleId);
+        AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
+        assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+        assertThat(finalRule.getOwner()).isEqualTo(new ComponentName("second", "owner"));
+
+        // Also, we discarded the "deleted rule" since we found it but decided not to use it.
+        assertThat(mZenModeHelper.mConfig.deletedRules).hasSize(0);
+    }
+
+    @Test
     @EnableFlags(FLAG_MODES_API)
     public void removeAutomaticZenRule_preservedForRestoringByPackageAndConditionId() {
         mContext.getTestablePermissions().setPermission(Manifest.permission.MANAGE_NOTIFICATIONS,
@@ -6758,6 +6877,52 @@
                 "Didn't find rule with id %s", ruleId);
     }
 
+    @Test
+    @DisableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void testDefaultConfig_preModesApi_rulesAreBare() {
+        // Create a new user, which should get a copy of the default policy.
+        mZenModeHelper.onUserSwitched(101);
+
+        ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
+                ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+
+        assertThat(eventsRule).isNotNull();
+        assertThat(eventsRule.zenPolicy).isNull();
+        assertThat(eventsRule.type).isEqualTo(TYPE_UNKNOWN);
+        assertThat(eventsRule.triggerDescription).isNull();
+    }
+
+    @Test
+    @EnableFlags(FLAG_MODES_API)
+    @DisableFlags(FLAG_MODES_UI)
+    public void testDefaultConfig_modesApi_rulesHaveFullPolicy() {
+        // Create a new user, which should get a copy of the default policy.
+        mZenModeHelper.onUserSwitched(201);
+
+        ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
+                ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+
+        assertThat(eventsRule).isNotNull();
+        assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+        assertThat(eventsRule.type).isEqualTo(TYPE_UNKNOWN);
+        assertThat(eventsRule.triggerDescription).isNull();
+    }
+
+    @Test
+    @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+    public void testDefaultConfig_modesUi_rulesHaveFullPolicy() {
+        // Create a new user, which should get a copy of the default policy.
+        mZenModeHelper.onUserSwitched(301);
+
+        ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
+                ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+
+        assertThat(eventsRule).isNotNull();
+        assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
+        assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_CALENDAR);
+        assertThat(eventsRule.triggerDescription).isNotEmpty();
+    }
+
     private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
             @Nullable ZenPolicy zenPolicy) {
         ZenRule rule = new ZenRule();
@@ -6809,7 +6974,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/Android.bp b/services/tests/wmtests/Android.bp
index 604869c..289f8a9 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -109,3 +109,35 @@
         ":OverlayTestApp",
     ],
 }
+
+test_module_config {
+    name: "WmTests_server_policy_Presubmit",
+    base: "WmTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.policy."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+}
+
+test_module_config {
+    name: "WmTests_server_policy",
+    base: "WmTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.policy."],
+}
+
+test_module_config {
+    name: "WmTests_wm_utils_Presubmit",
+    base: "WmTests",
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+    include_filters: ["com.android.server.wm.utils"],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
index d147325..0575d98 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -41,6 +41,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
 import android.view.KeyboardShortcutInfo;
@@ -50,6 +52,8 @@
 import com.android.internal.R;
 
 import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.Collections;
@@ -62,7 +66,13 @@
  */
 
 @SmallTest
+@EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR)
 public class ModifierShortcutManagerTests {
+
+    @ClassRule public static final SetFlagsRule.ClassRule SET_FLAGS_CLASS_RULE =
+            new SetFlagsRule.ClassRule();
+    @Rule public final SetFlagsRule mSetFlagsRule = SET_FLAGS_CLASS_RULE.createSetFlagsRule();
+
     private ModifierShortcutManager mModifierShortcutManager;
     private Handler mHandler;
     private Context mContext;
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 71f90a2..43171f8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -44,26 +44,21 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 
 @Presubmit
 @SmallTest
+@EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR)
 public class ModifierShortcutTests extends ShortcutKeyTestBase {
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
     private static final SparseArray<String> INTENT_SHORTCUTS =  new SparseArray<>();
     private static final SparseArray<String> ROLE_SHORTCUTS =  new SparseArray<>();
     static {
@@ -258,7 +253,7 @@
      * META+CTRL+BACKSPACE for taking a bugreport when the flag is enabled.
      */
     @Test
-    @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+    @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
     public void testTakeBugReport_flagEnabled() throws RemoteException {
         sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
         mPhoneWindowManager.assertTakeBugreport(true);
@@ -268,7 +263,7 @@
      * META+CTRL+BACKSPACE for taking a bugreport does nothing when the flag is disabledd.
      */
     @Test
-    @RequiresFlagsDisabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
+    @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
     public void testTakeBugReport_flagDisabled() throws RemoteException {
         sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
         mPhoneWindowManager.assertTakeBugreport(false);
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 07934ea..e694c0b 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -188,7 +188,7 @@
                 .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
         int[] outAppOp = new int[1];
         assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_WALLPAPER,
-                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
         assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_NONE);
     }
 
@@ -198,7 +198,7 @@
                 .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
         int[] outAppOp = new int[1];
         assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
-                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
         assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_CREATE_ACCESSIBILITY_OVERLAY);
     }
 
@@ -208,7 +208,7 @@
                 .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
         int[] outAppOp = new int[1];
         assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
-                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
         assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_NONE);
     }
 
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 ea825c7..1e035da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -705,7 +705,7 @@
         assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
 
         // Clear size compat.
-        activity.clearSizeCompatMode();
+        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
         activity.ensureActivityConfiguration();
         mDisplayContent.sendNewConfiguration();
 
@@ -1629,10 +1629,10 @@
     @Test
     public void testCompleteResume_updateCompatDisplayInsets() {
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
-        doReturn(true).when(activity).shouldCreateCompatDisplayInsets();
+        doReturn(true).when(activity).shouldCreateAppCompatDisplayInsets();
         activity.setState(RESUMED, "test");
         activity.completeResumeLocked();
-        assertNotNull(activity.getCompatDisplayInsets());
+        assertNotNull(activity.getAppCompatDisplayInsets());
     }
 
     /**
@@ -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/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index c788f3b..a7a08b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -209,7 +209,7 @@
     }
 
     void setShouldCreateCompatDisplayInsets(boolean enabled) {
-        doReturn(enabled).when(mActivityStack.top()).shouldCreateCompatDisplayInsets();
+        doReturn(enabled).when(mActivityStack.top()).shouldCreateAppCompatDisplayInsets();
     }
 
     void setTopActivityInSizeCompatMode(boolean inScm) {
@@ -499,7 +499,7 @@
             activity.setRequestedOrientation(screenOrientation);
         }
         // Make sure to use the provided configuration to construct the size compat fields.
-        activity.clearSizeCompatMode();
+        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
         activity.ensureActivityConfiguration();
         // Make sure the display configuration reflects the change of activity.
         if (activity.mDisplayContent.updateOrientation()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index af4394a..0c1fbf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -25,11 +25,15 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -37,6 +41,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -46,11 +51,14 @@
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.gui.DropInputMode;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -833,6 +841,353 @@
     }
 
     @Test
+    public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(activity);
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation run by the remote handler.
+        assertTrue(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord closingActivity = createActivityRecord(task);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+        // Make sure the TaskFragment is not embedded.
+        assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(closingActivity);
+        prepareActivityForAppTransition(openingActivity);
+        final int uid = 12345;
+        closingActivity.info.applicationInfo.uid = uid;
+        openingActivity.info.applicationInfo.uid = uid;
+        task.effectiveUid = uid;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity,
+                null /* changingTaskFragment */);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation is not run by the remote handler because the activity is filling the Task.
+        assertFalse(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord closingActivity = createActivityRecord(task);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+        // Make sure the TaskFragment is embedded.
+        taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        final Rect embeddedBounds = new Rect(task.getBounds());
+        embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
+        taskFragment.setBounds(embeddedBounds);
+        assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(closingActivity);
+        prepareActivityForAppTransition(openingActivity);
+        final int uid = 12345;
+        closingActivity.info.applicationInfo.uid = uid;
+        openingActivity.info.applicationInfo.uid = uid;
+        task.effectiveUid = uid;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity,
+                null /* changingTaskFragment */);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation run by the remote handler.
+        assertTrue(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Closing non-embedded activity.
+        final ActivityRecord closingActivity = createActivityRecord(task);
+        prepareActivityForAppTransition(closingActivity);
+        // Opening TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(openingActivity);
+        task.effectiveUid = openingActivity.getUid();
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation run by the remote handler.
+        assertTrue(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Closing TaskFragment with embedded activity.
+        final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
+        prepareActivityForAppTransition(closingActivity);
+        closingActivity.info.applicationInfo.uid = 12345;
+        // Opening TaskFragment with embedded activity with different UID.
+        final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord openingActivity = taskFragment2.getTopMostActivity();
+        prepareActivityForAppTransition(openingActivity);
+        openingActivity.info.applicationInfo.uid = 54321;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation run by the remote handler.
+        assertTrue(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Closing activity in Task1.
+        final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
+        prepareActivityForAppTransition(closingActivity);
+        // Opening TaskFragment with embedded activity in Task2.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(openingActivity);
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation not run by the remote handler.
+        assertFalse(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Closing TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(closingActivity);
+        closingActivity.info.applicationInfo.uid = 12345;
+        task.effectiveUid = closingActivity.getUid();
+        // Opening non-embedded activity with different UID.
+        final ActivityRecord openingActivity = createActivityRecord(task);
+        prepareActivityForAppTransition(openingActivity);
+        openingActivity.info.applicationInfo.uid = 54321;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation should not run by the remote handler when there are non-embedded activities of
+        // different UID.
+        assertFalse(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with embedded activity.
+        final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+        prepareActivityForAppTransition(activity);
+        // Set wallpaper as visible.
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // Animation should not run by the remote handler when there is wallpaper in the transition.
+        assertFalse(remoteAnimationRunner.isAnimationStarted());
+    }
+
+    @Test
+    public void testOverrideTaskFragmentAdapter_inputProtectedForUntrustedAnimation() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with embedded activities, one is trusted embedded, and the other
+        // one is untrusted embedded.
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .createActivityCount(2)
+                .setOrganizer(organizer)
+                .build();
+        final ActivityRecord activity0 = taskFragment.getChildAt(0).asActivityRecord();
+        final ActivityRecord activity1 = taskFragment.getChildAt(1).asActivityRecord();
+        // Also create a non-embedded activity in the Task.
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).build();
+        task.addChild(activity2, POSITION_BOTTOM);
+        prepareActivityForAppTransition(activity0);
+        prepareActivityForAppTransition(activity1);
+        prepareActivityForAppTransition(activity2);
+        doReturn(false).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity0);
+        doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity1);
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // The animation will be animated remotely by client and all activities are input disabled
+        // for untrusted animation.
+        assertTrue(remoteAnimationRunner.isAnimationStarted());
+        verify(activity0).setDropInputForAnimation(true);
+        verify(activity1).setDropInputForAnimation(true);
+        verify(activity2).setDropInputForAnimation(true);
+        verify(activity0).setDropInputMode(DropInputMode.ALL);
+        verify(activity1).setDropInputMode(DropInputMode.ALL);
+        verify(activity2).setDropInputMode(DropInputMode.ALL);
+
+        // Reset input after animation is finished.
+        clearInvocations(activity0);
+        clearInvocations(activity1);
+        clearInvocations(activity2);
+        remoteAnimationRunner.finishAnimation();
+
+        verify(activity0).setDropInputForAnimation(false);
+        verify(activity1).setDropInputForAnimation(false);
+        verify(activity2).setDropInputForAnimation(false);
+        verify(activity0).setDropInputMode(DropInputMode.OBSCURED);
+        verify(activity1).setDropInputMode(DropInputMode.NONE);
+        verify(activity2).setDropInputMode(DropInputMode.NONE);
+    }
+
+    /**
+     * Since we don't have any use case to rely on handling input during animation, disable it even
+     * if it is trusted embedding so that it could cover some edge-cases when a previously trusted
+     * host starts doing something bad.
+     */
+    @Test
+    public void testOverrideTaskFragmentAdapter_inputProtectedForTrustedAnimation() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with only trusted embedded activity
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .createActivityCount(1)
+                .setOrganizer(organizer)
+                .build();
+        final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
+        prepareActivityForAppTransition(activity);
+        doReturn(true).when(taskFragment).isAllowedToEmbedActivityInTrustedMode(activity);
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // The animation will be animated remotely by client and all activities are input disabled
+        // for untrusted animation.
+        assertTrue(remoteAnimationRunner.isAnimationStarted());
+        verify(activity).setDropInputForAnimation(true);
+        verify(activity).setDropInputMode(DropInputMode.ALL);
+
+        // Reset input after animation is finished.
+        clearInvocations(activity);
+        remoteAnimationRunner.finishAnimation();
+
+        verify(activity).setDropInputForAnimation(false);
+        verify(activity).setDropInputMode(DropInputMode.NONE);
+    }
+
+    /**
+     * We don't need to drop input for fully trusted embedding (system app, and embedding in the
+     * same app). This will allow users to do fast tapping.
+     */
+    @Test
+    public void testOverrideTaskFragmentAdapter_noInputProtectedForFullyTrustedAnimation() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+        setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+        // Create a TaskFragment with only trusted embedded activity
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .createActivityCount(1)
+                .setOrganizer(organizer)
+                .build();
+        final ActivityRecord activity = taskFragment.getChildAt(0).asActivityRecord();
+        prepareActivityForAppTransition(activity);
+        final int uid = mAtm.mTaskFragmentOrganizerController.getTaskFragmentOrganizerUid(
+                getITaskFragmentOrganizer(organizer));
+        doReturn(true).when(task).isFullyTrustedEmbedding(uid);
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare and start transition.
+        prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+        mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+        // The animation will be animated remotely by client, but input should not be dropped for
+        // fully trusted.
+        assertTrue(remoteAnimationRunner.isAnimationStarted());
+        verify(activity, never()).setDropInputForAnimation(true);
+        verify(activity, never()).setDropInputMode(DropInputMode.ALL);
+    }
+
+    @Test
     public void testTransitionGoodToGoForTaskFragments() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final Task task = createTask(mDisplayContent);
@@ -898,6 +1253,22 @@
         verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
     }
 
+    /** Registers remote animation for the organizer. */
+    private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
+            TestRemoteAnimationRunner remoteAnimationRunner) {
+        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+                remoteAnimationRunner, 10, 1);
+        final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
+        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
+        definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
+        registerTaskFragmentOrganizer(iOrganizer);
+        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
+    }
+
     private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
             TaskFragmentOrganizer organizer) {
         return ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 9950541..b6e393d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -39,10 +39,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
@@ -51,9 +49,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 
 import android.graphics.Rect;
 import android.os.Binder;
@@ -377,41 +373,6 @@
     }
 
     @Test
-    public void testDelayWhileRecents() {
-        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
-        doReturn(false).when(dc).onDescendantOrientationChanged(any());
-        final Task task = createTask(dc);
-
-        // Simulate activity1 launches activity2.
-        final ActivityRecord activity1 = createActivityRecord(task);
-        activity1.setVisible(true);
-        activity1.setVisibleRequested(false);
-        activity1.allDrawn = true;
-        final ActivityRecord activity2 = createActivityRecord(task);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(true);
-        activity2.allDrawn = true;
-
-        dc.mClosingApps.add(activity1);
-        dc.mOpeningApps.add(activity2);
-        dc.prepareAppTransition(TRANSIT_OPEN);
-        assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
-
-        // Wait until everything in animation handler get executed to prevent the exiting window
-        // from being removed during WindowSurfacePlacer Traversal.
-        waitUntilHandlersIdle();
-
-        // Start recents
-        doReturn(true).when(task)
-                .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
-
-        dc.mAppTransitionController.handleAppTransitionReady();
-
-        verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
-        verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
-    }
-
-    @Test
     public void testGetAnimationStyleResId() {
         // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
         // specifying window type.
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
index 6e48818..3910904 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
 
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT;
@@ -25,9 +26,11 @@
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -43,6 +46,9 @@
 import android.content.pm.PackageManagerInternal;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.DeviceConfig;
 import android.util.Pair;
 
@@ -52,6 +58,7 @@
 import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
+import com.android.window.flags.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -95,7 +102,9 @@
     @Rule
     public final ExtendedMockitoRule extendedMockitoRule =
             new ExtendedMockitoRule.Builder(this).setStrictness(Strictness.LENIENT).build();
-
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
     TestableBackgroundActivityStartController mController;
     @Mock
     ActivityMetricsLogger mActivityMetricsLogger;
@@ -186,7 +195,7 @@
         when(mAppOpsManager.checkOpNoThrow(
                 eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION),
                 anyInt(), anyString())).thenReturn(AppOpsManager.MODE_DEFAULT);
-        when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+        when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
                 BalVerdict.BLOCK);
     }
 
@@ -227,7 +236,7 @@
         // call
         BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
                 balState);
-        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
                 balState);
 
         balState.setResultForCaller(callerVerdict);
@@ -295,7 +304,77 @@
                 checkedOptions);
 
         // call
-        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
+                balState);
+        balState.setResultForRealCaller(realCallerVerdict);
+
+        // assertions
+        assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo(
+                BAL_ALLOW_VISIBLE_WINDOW);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
+    public void testCaller_appHasVisibleWindowWithIfVisibleOptIn() {
+        int callingUid = REGULAR_UID_1;
+        int callingPid = REGULAR_PID_1;
+        final String callingPackage = REGULAR_PACKAGE_1;
+        int realCallingUid = REGULAR_UID_2;
+        int realCallingPid = REGULAR_PID_2;
+
+        // setup state
+        when(mService.hasActiveVisibleWindow(eq(callingUid))).thenReturn(true);
+        when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
+
+        // prepare call
+        PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+        BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+        Intent intent = TEST_INTENT;
+        ActivityOptions checkedOptions = mCheckedOptions
+                .setPendingIntentCreatorBackgroundActivityStartMode(
+                        MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
+        BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+                callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
+                originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+                checkedOptions);
+
+        // call
+        BalVerdict callerVerdict = mController.checkBackgroundActivityStartAllowedByCaller(
+                balState);
+        balState.setResultForCaller(callerVerdict);
+
+        // assertions
+        assertWithMessage(balState.toString()).that(callerVerdict.getCode()).isEqualTo(
+                BAL_ALLOW_VISIBLE_WINDOW);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
+    public void testRealCaller_appHasVisibleWindowWithIfVisibleOptIn() {
+        int callingUid = REGULAR_UID_1;
+        int callingPid = REGULAR_PID_1;
+        final String callingPackage = REGULAR_PACKAGE_1;
+        int realCallingUid = REGULAR_UID_2;
+        int realCallingPid = REGULAR_PID_2;
+
+        // setup state
+        when(mService.hasActiveVisibleWindow(eq(realCallingUid))).thenReturn(true);
+        when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
+
+        // prepare call
+        PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+        BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+        Intent intent = TEST_INTENT;
+        ActivityOptions checkedOptions = mCheckedOptions
+                .setPendingIntentCreatorBackgroundActivityStartMode(
+                        MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
+        BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+                callingPid, callingPackage, realCallingUid, realCallingPid, mCallerApp,
+                originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+                checkedOptions);
+
+        // call
+        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
                 balState);
         balState.setResultForRealCaller(realCallerVerdict);
 
@@ -320,7 +399,7 @@
         int realCallingPid = REGULAR_PID_2;
 
         // setup state
-        when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+        when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
                 new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
         when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
 
@@ -357,7 +436,7 @@
                 mService.getProcessController(eq(realCallingPid), eq(realCallingUid))).thenReturn(
                 mCallerApp);
         when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
-        when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+        when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
                 new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
 
         // prepare call
@@ -371,7 +450,7 @@
                 checkedOptions);
 
         // call
-        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
                 balState);
         balState.setResultForRealCaller(realCallerVerdict);
 
@@ -404,9 +483,9 @@
                 mService.getProcessController(eq(realCallingPid), eq(realCallingUid))).thenReturn(
                 mCallerApp);
         when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW);
-        when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+        when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
                 BalVerdict.BLOCK);
-        when(otherProcess.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+        when(otherProcess.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
                 new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed"));
 
         // prepare call
@@ -420,7 +499,7 @@
                 checkedOptions);
 
         // call
-        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
                 balState);
         balState.setResultForRealCaller(realCallerVerdict);
 
@@ -456,7 +535,7 @@
                 checkedOptions);
 
         // call
-        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
                 balState);
         balState.setResultForRealCaller(realCallerVerdict);
 
@@ -466,6 +545,45 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
+    public void testRealCaller_isCompanionAppWithOptInIfVisible() {
+        // The app has a service that is bound by a different, visible app. The app bound to the
+        // service must remain visible for the app in the background to start activities
+        // successfully.
+        int callingUid = REGULAR_UID_1;
+        int callingPid = REGULAR_PID_1;
+        final String callingPackage = REGULAR_PACKAGE_1;
+        int realCallingUid = REGULAR_UID_2;
+        int realCallingPid = REGULAR_PID_2;
+
+        // setup state
+        final int realCallingUserId = UserHandle.getUserId(realCallingUid);
+        when(mService.isAssociatedCompanionApp(eq(realCallingUserId),
+                eq(realCallingUid))).thenReturn(true);
+
+        // prepare call
+        PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+        BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+        Intent intent = TEST_INTENT;
+        ActivityOptions checkedOptions = mCheckedOptions
+                .setPendingIntentBackgroundActivityStartMode(
+                        MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE);
+        BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
+                callingPid, callingPackage, realCallingUid, realCallingPid, null,
+                originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+                checkedOptions);
+
+        // call
+        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
+                balState);
+        balState.setResultForRealCaller(realCallerVerdict);
+
+        // assertions
+        assertWithMessage(balState.toString()).that(realCallerVerdict.getCode()).isEqualTo(
+                BAL_BLOCK);
+    }
+
+    @Test
     public void testCaller_balPermission() {
         int callingUid = REGULAR_UID_1;
         int callingPid = REGULAR_PID_1;
@@ -523,7 +641,7 @@
                 checkedOptions);
 
         // call
-        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
+        BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedByRealCaller(
                 balState);
         balState.setResultForRealCaller(realCallerVerdict);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index e364264..6ec7895 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -24,6 +24,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -43,6 +44,7 @@
 import com.android.compatibility.common.util.DeviceConfigStateHelper;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
+import com.android.server.wm.BackgroundLaunchProcessController.BalCheckConfiguration;
 
 import org.junit.After;
 import org.junit.Before;
@@ -167,9 +169,9 @@
         }
 
         @Override
-        BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
+        BalVerdict checkBackgroundActivityStartAllowedByRealCaller(BalState state) {
             return mRealCallerVerdict.orElseGet(
-                    () -> super.checkBackgroundActivityStartAllowedBySender(state));
+                    () -> super.checkBackgroundActivityStartAllowedByRealCaller(state));
         }
 
         public void setRealCallerVerdict(BalVerdict verdict) {
@@ -177,11 +179,12 @@
         }
 
         @Override
-        BalVerdict checkProcessAllowsBal(WindowProcessController app, BalState state) {
+        BalVerdict checkProcessAllowsBal(WindowProcessController app, BalState state,
+                BalCheckConfiguration checkConfiguration) {
             if (mProcessVerdicts.containsKey(app)) {
                 return mProcessVerdicts.get(app);
             }
-            return super.checkProcessAllowsBal(app, state);
+            return super.checkProcessAllowsBal(app, state, checkConfiguration);
         }
     }
 
@@ -209,7 +212,7 @@
         Mockito.when(mAppOpsManager.checkOpNoThrow(
                 eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION),
                 anyInt(), anyString())).thenReturn(AppOpsManager.MODE_DEFAULT);
-        Mockito.when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
+        Mockito.when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn(
                 BalVerdict.BLOCK);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
index c9c7e92..27e147d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundLaunchProcessControllerTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_BOUND_BY_FOREGROUND;
@@ -95,7 +96,12 @@
     int mUid = 234;
     String mPackageName = "package.name";
     int mAppSwitchState = APP_SWITCH_DISALLOW;
-    boolean mIsCheckingForFgsStart = false;
+    BackgroundLaunchProcessController.BalCheckConfiguration mBalCheckConfiguration =
+            new BackgroundLaunchProcessController.BalCheckConfiguration(
+                    /* isCheckingForFgsStarts */ false,
+                    /* checkVisibility */ true,
+                    /* checkOtherExemptions */ true,
+                    ACTIVITY_BG_START_GRACE_PERIOD_MS);
     boolean mHasActivityInVisibleTask = false;
     boolean mHasBackgroundActivityStartPrivileges = false;
     long mLastStopAppSwitchesTime = 0L;
@@ -106,7 +112,7 @@
     public void testNothingAllows() {
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
@@ -118,7 +124,7 @@
         mHasBackgroundActivityStartPrivileges = true;
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
@@ -136,7 +142,7 @@
                 BackgroundStartPrivileges.ALLOW_BAL);
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
@@ -154,7 +160,7 @@
                 BackgroundStartPrivileges.ALLOW_BAL);
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
@@ -170,7 +176,7 @@
                 BackgroundStartPrivileges.ALLOW_BAL);
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
@@ -186,7 +192,7 @@
                 BackgroundStartPrivileges.ALLOW_BAL);
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
@@ -201,7 +207,7 @@
         mHasActiveVisibleWindow.add(999);
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
@@ -216,7 +222,7 @@
         mHasActiveVisibleWindow.add(999);
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
@@ -229,7 +235,7 @@
         mHasActivityInVisibleTask = true;
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
@@ -245,7 +251,7 @@
         mLastActivityFinishTime = now - 100;
         BalVerdict balVerdict = mController.areBackgroundActivityStartsAllowed(
                 mPid, mUid, mPackageName,
-                mAppSwitchState, mIsCheckingForFgsStart,
+                mAppSwitchState, mBalCheckConfiguration,
                 mHasActivityInVisibleTask, mHasBackgroundActivityStartPrivileges,
                 mLastStopAppSwitchesTime, mLastActivityLaunchTime,
                 mLastActivityFinishTime);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index affaad6..14276ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -277,7 +277,7 @@
         mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true);
 
         mWmInternal.waitForAllWindowsDrawn(mScreenUnblocker,
-                /* timeout= */ Integer.MAX_VALUE, DEFAULT_DISPLAY);
+                /* timeout= */ Integer.MAX_VALUE, INVALID_DISPLAY);
         mWmInternal.waitForAllWindowsDrawn(mSecondaryScreenUnblocker,
                 /* timeout= */ Integer.MAX_VALUE, mSecondaryDisplayContent.getDisplayId());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 58e919d..eca4d21 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1144,6 +1144,7 @@
 
     @Test
     public void testOrientationBehind() {
+        assertNull(mDisplayContent.getLastOrientationSource());
         final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
         prev.setVisibleRequested(false);
@@ -1732,25 +1733,6 @@
         assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
     }
 
-    @SetupWindows(addWindows = W_ACTIVITY)
-    @Test
-    public void testRotateSeamlesslyWithFixedRotation() {
-        final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
-        final ActivityRecord app = mAppWindow.mActivityRecord;
-        mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
-        mAppWindow.mAttrs.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
-
-        // Use seamless rotation if the top app is rotated.
-        assertTrue(displayRotation.shouldRotateSeamlessly(ROTATION_0 /* oldRotation */,
-                ROTATION_90 /* newRotation */, false /* forceUpdate */));
-
-        mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(app);
-
-        // Use normal rotation because animating recents is an intermediate state.
-        assertFalse(displayRotation.shouldRotateSeamlessly(ROTATION_0 /* oldRotation */,
-                ROTATION_90 /* newRotation */, false /* forceUpdate */));
-    }
-
     @Test
     public void testFixedRotationWithPip() {
         final DisplayContent displayContent = mDefaultDisplay;
@@ -1828,49 +1810,6 @@
         assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
     }
 
-    @Test
-    public void testRecentsNotRotatingWithFixedRotation() {
-        unblockDisplayRotation(mDisplayContent);
-        final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
-        // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
-        doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
-        final ActivityRecord activity = createActivityRecord(mDisplayContent);
-        final ActivityRecord recentsActivity = createActivityRecord(mDisplayContent);
-        recentsActivity.setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
-        doReturn(mock(RecentsAnimationController.class)).when(mWm).getRecentsAnimationController();
-
-        // Do not rotate if the recents animation is animating on top.
-        mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
-        displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
-        assertFalse(displayRotation.updateRotationUnchecked(false));
-
-        // Rotation can be updated if the recents animation is finished.
-        mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
-        assertTrue(displayRotation.updateRotationUnchecked(false));
-
-        // Rotation can be updated if the policy is not ok to animate (e.g. going to sleep).
-        mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
-        displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
-        ((TestWindowManagerPolicy) mWm.mPolicy).mOkToAnimate = false;
-        assertTrue(displayRotation.updateRotationUnchecked(false));
-
-        // Rotation can be updated if the recents animation is animating but it is not on top, e.g.
-        // switching activities in different orientations by quickstep gesture.
-        mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
-        mDisplayContent.setFixedRotationLaunchingAppUnchecked(activity);
-        displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
-        assertTrue(displayRotation.updateRotationUnchecked(false));
-
-        // The recents activity should not apply fixed rotation if the top activity is not opaque.
-        mDisplayContent.mFocusedApp = activity;
-        doReturn(false).when(mDisplayContent.mFocusedApp).occludesParent();
-        doReturn(ROTATION_90).when(mDisplayContent).rotationForActivityInDifferentOrientation(
-                eq(recentsActivity));
-        mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
-        assertFalse(recentsActivity.hasFixedRotationTransform());
-    }
-
     @EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION)
     @Test
     public void testRespectNonTopVisibleFixedOrientation() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index 57839e2..ffe3893 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -244,6 +244,19 @@
         mWm.clearForcedDisplaySize(mSecondaryDisplay.getDisplayId());
         assertEquals(mSecondaryDisplay.mInitialDisplayWidth, mSecondaryDisplay.mBaseDisplayWidth);
         assertEquals(mSecondaryDisplay.mInitialDisplayHeight, mSecondaryDisplay.mBaseDisplayHeight);
+
+        // Forced size can be cleared even if the initial display size is smaller than max width.
+        final int maxWidth = mSecondaryDisplay.mInitialDisplayWidth - 20;
+        mSecondaryDisplay.setMaxUiWidth(maxWidth);
+        assertEquals(maxWidth, mSecondaryDisplay.mBaseDisplayWidth);
+        mSecondaryDisplay.setForcedSize(maxWidth - 10, maxWidth + 10);
+        assertNotEquals(maxWidth, mSecondaryDisplay.mBaseDisplayHeight);
+        assertTrue(mSecondaryDisplay.mIsSizeForced);
+
+        mWm.clearForcedDisplaySize(mSecondaryDisplay.mDisplayId);
+        assertFalse(mSecondaryDisplay.mIsSizeForced);
+        assertEquals(maxWidth, mSecondaryDisplay.mBaseDisplayWidth);
+        assertEquals(0, mSettingsProvider.getSettings(originalInfo).mForcedWidth);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index d8d5729..ea175a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -24,6 +24,8 @@
 
 import android.graphics.PixelFormat;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 
 import androidx.test.filters.SmallTest;
@@ -72,6 +74,7 @@
      * Checks that scheduling with all the state set and manually triggering the show does succeed.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testScheduleShowIme() {
         final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
         makeWindowVisibleAndDrawn(ime);
@@ -99,6 +102,7 @@
      * all the state becomes available.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testScheduleShowIme_noInitialState() {
         final WindowState target = createWindow(null, TYPE_APPLICATION, "app");
 
@@ -126,6 +130,7 @@
      * does continue and succeed when the runnable is started.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testScheduleShowIme_delayedAfterPrepareSurfaces() {
         final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
         makeWindowVisibleAndDrawn(ime);
@@ -158,6 +163,7 @@
      * when the surface placement happens.
      */
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
     public void testScheduleShowIme_delayedSurfacePlacement() {
         final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
         makeWindowVisibleAndDrawn(ime);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0dc56f8..964264d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -32,6 +32,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -202,6 +203,11 @@
         getController().onImeControlTargetChanged(base);
         base.setRequestedVisibleTypes(ime(), ime());
         getController().onRequestedVisibleTypesChanged(base, null /* statsToken */);
+        if (android.view.inputmethod.Flags.refactorInsetsController()) {
+            // to set the serverVisibility, the IME needs to be drawn and onPostLayout be called.
+            mImeWindow.mWinAnimator.mDrawState = HAS_DRAWN;
+            getController().onPostLayout();
+        }
 
         // Send our spy window (app) into the system so that we can detect the invocation.
         final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
@@ -500,6 +506,12 @@
         getController().onRequestedVisibleTypesChanged(app, null /* statsToken */);
         assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
 
+        if (android.view.inputmethod.Flags.refactorInsetsController()) {
+            // The IME is only set to shown, after onPostLayout is called and all preconditions
+            // (serverVisible, no givenInsetsPending, etc.) are fulfilled
+            getController().getImeSourceProvider().onPostLayout();
+        }
+
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
         assertNotNull(app.getInsetsState().peekSource(ID_IME));
         verify(app, atLeastOnce()).notifyInsetsChanged();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
deleted file mode 100644
index 63e3e5c..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ /dev/null
@@ -1,834 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
-import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
-import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.platform.test.annotations.Presubmit;
-import android.util.SparseBooleanArray;
-import android.view.IRecentsAnimationRunner;
-import android.view.SurfaceControl;
-import android.view.WindowManager.LayoutParams;
-import android.window.TaskSnapshot;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-
-import com.google.common.truth.Truth;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-
-/**
- * Build/Install/Run:
- *  atest WmTests:RecentsAnimationControllerTest
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class RecentsAnimationControllerTest extends WindowTestsBase {
-
-    @Mock SurfaceControl mMockLeash;
-    @Mock SurfaceControl.Transaction mMockTransaction;
-    @Mock OnAnimationFinishedCallback mFinishedCallback;
-    @Mock IRecentsAnimationRunner mMockRunner;
-    @Mock RecentsAnimationController.RecentsAnimationCallbacks mAnimationCallbacks;
-    @Mock TaskSnapshot mMockTaskSnapshot;
-    private RecentsAnimationController mController;
-    private Task mRootHomeTask;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        doNothing().when(mWm.mRoot).performSurfacePlacement();
-        when(mMockRunner.asBinder()).thenReturn(new Binder());
-        mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
-                DEFAULT_DISPLAY));
-        mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask();
-        assertNotNull(mRootHomeTask);
-    }
-
-    @Test
-    public void testRemovedBeforeStarted_expectCanceled() throws Exception {
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        AnimationAdapter adapter = mController.addAnimation(activity.getTask(),
-                false /* isRecentTaskInvisible */);
-        adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
-                mFinishedCallback);
-
-        // The activity doesn't contain window so the animation target cannot be created.
-        mController.startAnimation();
-
-        // Verify that the finish callback to reparent the leash is called
-        verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), eq(adapter));
-        // Verify the animation canceled callback to the app was made
-        verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
-        verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
-    }
-
-    @Test
-    public void testCancelAfterRemove_expectIgnored() {
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        AnimationAdapter adapter = mController.addAnimation(activity.getTask(),
-                false /* isRecentTaskInvisible */);
-        adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
-                mFinishedCallback);
-
-        // Remove the app window so that the animation target can not be created
-        activity.removeImmediately();
-        mController.startAnimation();
-        mController.cleanupAnimation(REORDER_KEEP_IN_PLACE);
-        try {
-            mController.cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "test");
-        } catch (Exception e) {
-            fail("Unexpected failure when canceling animation after finishing it");
-        }
-    }
-
-    @Test
-    public void testIncludedApps_expectTargetAndVisible() {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord homeActivity = createHomeActivity();
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final ActivityRecord hiddenActivity = createActivityRecord(mDefaultDisplay);
-        hiddenActivity.setVisible(false);
-        mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
-                mDefaultDisplay.getRotation());
-        initializeRecentsAnimationController(mController, homeActivity);
-
-        // Ensure that we are animating the target activity as well
-        assertTrue(mController.isAnimatingTask(homeActivity.getTask()));
-        assertTrue(mController.isAnimatingTask(activity.getTask()));
-        assertFalse(mController.isAnimatingTask(hiddenActivity.getTask()));
-    }
-
-    @Test
-    public void testLaunchAndStartRecents_expectTargetAndVisible() throws Exception {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord homeActivity = createHomeActivity();
-        final Task task = createTask(mDefaultDisplay);
-        // Emulate that activity1 has just launched activity2, but app transition has not yet been
-        // executed.
-        final ActivityRecord activity1 = createActivityRecord(task);
-        activity1.setVisible(true);
-        activity1.setVisibleRequested(false);
-        activity1.addWindow(createWindowState(new LayoutParams(TYPE_BASE_APPLICATION), activity1));
-
-        final ActivityRecord activity2 = createActivityRecord(task);
-        activity2.setVisible(false);
-        activity2.setVisibleRequested(true);
-
-        mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
-                mDefaultDisplay.getRotation());
-        initializeRecentsAnimationController(mController, homeActivity);
-        mController.startAnimation();
-        verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */,
-                null /* taskSnapshots */);
-    }
-
-    @Test
-    public void testWallpaperIncluded_expectTarget() throws Exception {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord homeActivity = createHomeActivity();
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
-                mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
-        spyOn(mDefaultDisplay.mWallpaperController);
-        doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
-
-        mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
-                mDefaultDisplay.getRotation());
-        initializeRecentsAnimationController(mController, homeActivity);
-        mController.startAnimation();
-
-        // Ensure that we are animating the app and wallpaper target
-        assertTrue(mController.isAnimatingTask(activity.getTask()));
-        assertTrue(mController.isAnimatingWallpaper(wallpaperWindowToken));
-    }
-
-    @Test
-    public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord homeActivity = createHomeActivity();
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
-                mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
-        spyOn(mDefaultDisplay.mWallpaperController);
-        doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
-
-        mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
-                mDefaultDisplay.getRotation());
-        initializeRecentsAnimationController(mController, homeActivity);
-        mController.startAnimation();
-
-        // Cancel the animation and ensure the controller is still running
-        wallpaperWindowToken.cancelAnimation();
-        assertTrue(mController.isAnimatingTask(activity.getTask()));
-        assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken));
-        verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */,
-                null /* taskSnapshots */);
-    }
-
-    @Test
-    public void testFinish_expectTargetAndWallpaperAdaptersRemoved() {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord homeActivity = createHomeActivity();
-        final WindowState hwin1 = createWindow(null, TYPE_BASE_APPLICATION, homeActivity, "hwin1");
-        homeActivity.addWindow(hwin1);
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
-                mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
-        spyOn(mDefaultDisplay.mWallpaperController);
-        doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
-
-        // Start and finish the animation
-        initializeRecentsAnimationController(mController, homeActivity);
-        mController.startAnimation();
-
-        assertTrue(mController.isAnimatingTask(homeActivity.getTask()));
-        assertTrue(mController.isAnimatingTask(activity.getTask()));
-
-        // Reset at this point since we may remove adapters that couldn't be created
-        clearInvocations(mController);
-        mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
-
-        // Ensure that we remove the task (home & app) and wallpaper adapters
-        verify(mController, times(2)).removeAnimation(any());
-        verify(mController, times(1)).removeWallpaperAnimation(any());
-    }
-
-    @Test
-    public void testDeferCancelAnimation() throws Exception {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-        assertEquals(activity.getTask().getTopVisibleActivity(), activity);
-        assertEquals(activity.findMainWindow(), win1);
-
-        mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
-        assertTrue(mController.isAnimatingTask(activity.getTask()));
-
-        mController.setDeferredCancel(true /* deferred */, false /* screenshot */);
-        mController.cancelAnimationWithScreenshot(false /* screenshot */);
-        verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
-
-        // Simulate the app transition finishing
-        mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0);
-        verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
-    }
-
-    @Test
-    public void testDeferCancelAnimationWithScreenShot() throws Exception {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-        assertEquals(activity.getTask().getTopVisibleActivity(), activity);
-        assertEquals(activity.findMainWindow(), win1);
-
-        RecentsAnimationController.TaskAnimationAdapter adapter = mController.addAnimation(
-                activity.getTask(), false /* isRecentTaskInvisible */);
-        assertTrue(mController.isAnimatingTask(activity.getTask()));
-
-        spyOn(mWm.mTaskSnapshotController);
-        doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
-                anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
-        mController.setDeferredCancel(true /* deferred */, true /* screenshot */);
-        mController.cancelAnimationWithScreenshot(true /* screenshot */);
-        verify(mMockRunner).onAnimationCanceled(any(int[].class) /* taskIds */,
-                any(TaskSnapshot[].class) /* taskSnapshots */);
-
-        // Continue the animation (simulating a call to cleanupScreenshot())
-        mController.continueDeferredCancelAnimation();
-        verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
-    }
-
-    @Test
-    public void testShouldAnimateWhenNoCancelWithDeferredScreenshot() {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-        assertEquals(activity.getTask().getTopVisibleActivity(), activity);
-        assertEquals(activity.findMainWindow(), win1);
-
-        mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
-        assertTrue(mController.isAnimatingTask(activity.getTask()));
-
-        // Assume activity transition should animate when no
-        // IRecentsAnimationController#setDeferCancelUntilNextTransition called.
-        assertFalse(mController.shouldDeferCancelWithScreenshot());
-        assertTrue(activity.shouldAnimate());
-    }
-
-    @Test
-    public void testBinderDiedAfterCancelWithDeferredScreenshot() throws Exception {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord homeActivity = createHomeActivity();
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-
-        initializeRecentsAnimationController(mController, homeActivity);
-        mController.setWillFinishToHome(true);
-
-        // Verify cancel is called with a snapshot and that we've created an overlay
-        spyOn(mWm.mTaskSnapshotController);
-        doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
-                anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
-        mController.cancelAnimationWithScreenshot(true /* screenshot */);
-        verify(mMockRunner).onAnimationCanceled(any(), any());
-
-        // Simulate process crashing and ensure the animation is still canceled
-        mController.binderDied();
-        verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
-    }
-
-    @Test
-    public void testRecentViewInFixedPortraitWhenTopAppInLandscape() {
-        makeDisplayPortrait(mDefaultDisplay);
-        unblockDisplayRotation(mDefaultDisplay);
-        mWm.setRecentsAnimationController(mController);
-
-        final ActivityRecord homeActivity = createHomeActivity();
-        homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-
-        final ActivityRecord landActivity = createActivityRecord(mDefaultDisplay);
-        landActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, landActivity, "win1");
-        landActivity.addWindow(win1);
-
-        assertEquals(landActivity.getTask().getTopVisibleActivity(), landActivity);
-        assertEquals(landActivity.findMainWindow(), win1);
-
-        // Ensure that the display is in Landscape
-        landActivity.onDescendantOrientationChanged(landActivity);
-        assertEquals(Configuration.ORIENTATION_LANDSCAPE,
-                mDefaultDisplay.getConfiguration().orientation);
-
-        initializeRecentsAnimationController(mController, homeActivity);
-
-        assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity));
-
-        // Check that the home app is in portrait
-        assertEquals(Configuration.ORIENTATION_PORTRAIT,
-                homeActivity.getConfiguration().orientation);
-
-        // Home activity won't become top (return to landActivity), so the top rotated record should
-        // be cleared.
-        mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-        assertFalse(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity));
-        assertFalse(mDefaultDisplay.hasTopFixedRotationLaunchingApp());
-        // The transform should keep until the transition is done, so the restored configuration
-        // won't be sent to activity and cause unnecessary configuration change.
-        assertTrue(homeActivity.hasFixedRotationTransform());
-
-        // In real case the transition will be executed from RecentsAnimation#finishAnimation.
-        mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(
-                homeActivity.token);
-        assertFalse(homeActivity.hasFixedRotationTransform());
-    }
-
-    private ActivityRecord prepareFixedRotationLaunchingAppWithRecentsAnim() {
-        final ActivityRecord homeActivity = createHomeActivity();
-        homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        // Add a window so it can be animated by the recents.
-        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
-        activity.addWindow(win);
-        // Assume an activity is launching to different rotation.
-        mDefaultDisplay.setFixedRotationLaunchingApp(activity,
-                (mDefaultDisplay.getRotation() + 1) % 4);
-
-        assertTrue(activity.hasFixedRotationTransform());
-        assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(activity));
-
-        // Before the transition is done, the recents animation is triggered.
-        initializeRecentsAnimationController(mController, homeActivity);
-        assertFalse(homeActivity.hasFixedRotationTransform());
-        assertTrue(mController.isAnimatingTask(activity.getTask()));
-
-        return activity;
-    }
-
-    @Test
-    public void testClearFixedRotationLaunchingAppAfterCleanupAnimation() {
-        final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim();
-
-        // Simulate giving up the swipe up gesture to keep the original activity as top.
-        mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-        // The rotation transform should be cleared after updating orientation with display.
-        assertTopFixedRotationLaunchingAppCleared(activity);
-
-        // Simulate swiping up recents (home) in different rotation.
-        final ActivityRecord home = mDefaultDisplay.getDefaultTaskDisplayArea().getHomeActivity();
-        startRecentsInDifferentRotation(home);
-
-        // If the recents activity becomes the top running activity (e.g. the original top activity
-        // is either finishing or moved to back during recents animation), the display orientation
-        // will be determined by it so the fixed rotation must be cleared.
-        activity.finishing = true;
-        mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-        assertTopFixedRotationLaunchingAppCleared(home);
-
-        startRecentsInDifferentRotation(home);
-        // Assume recents activity becomes invisible for some reason (e.g. screen off).
-        home.setVisible(false);
-        mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-        // Although there won't be a transition finish callback, the fixed rotation must be cleared.
-        assertTopFixedRotationLaunchingAppCleared(home);
-    }
-
-    @Test
-    public void testKeepFixedRotationWhenMovingRecentsToTop() {
-        final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim();
-        // Assume a transition animation has started running before recents animation. Then the
-        // activity will receive onAnimationFinished that notifies app transition finished when
-        // removing the recents animation of task.
-        activity.getTask().getAnimationSources().add(activity);
-
-        // Simulate swiping to home/recents before the transition is done.
-        mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
-        // The rotation transform should be preserved. In real case, it will be cleared by the next
-        // move-to-top transition.
-        assertTrue(activity.hasFixedRotationTransform());
-    }
-
-    @Test
-    public void testCheckRotationAfterCleanup() {
-        mWm.setRecentsAnimationController(mController);
-        spyOn(mDisplayContent.mFixedRotationTransitionListener);
-        final ActivityRecord recents = mock(ActivityRecord.class);
-        recents.setOverrideOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
-        doReturn(ORIENTATION_PORTRAIT).when(recents)
-                .getRequestedConfigurationOrientation(anyBoolean());
-        mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recents);
-
-        // Rotation update is skipped while the recents animation is running.
-        final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
-        final int topOrientation = DisplayContentTests.getRotatedOrientation(mDefaultDisplay);
-        assertFalse(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */));
-        assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
-        final int prevRotation = mDisplayContent.getRotation();
-        mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-
-        // In real case, it is called from RecentsAnimation#finishAnimation -> continueWindowLayout
-        // -> handleAppTransitionReady -> add FINISH_LAYOUT_REDO_CONFIG, and DisplayContent#
-        // applySurfaceChangesTransaction will call updateOrientation for FINISH_LAYOUT_REDO_CONFIG.
-        assertTrue(displayRotation.updateOrientation(topOrientation, false  /* forceUpdate */));
-        // The display should be updated to the changed orientation after the animation is finished.
-        assertNotEquals(displayRotation.getRotation(), prevRotation);
-    }
-
-    @Test
-    public void testWallpaperHasFixedRotationApplied() {
-        makeDisplayPortrait(mDefaultDisplay);
-        unblockDisplayRotation(mDefaultDisplay);
-        mWm.setRecentsAnimationController(mController);
-
-        // Create a portrait home activity, a wallpaper and a landscape activity displayed on top.
-        final ActivityRecord homeActivity = createHomeActivity();
-        homeActivity.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-
-        final WindowState homeWindow = createWindow(null, TYPE_BASE_APPLICATION, homeActivity,
-                "homeWindow");
-        makeWindowVisible(homeWindow);
-        homeActivity.addWindow(homeWindow);
-        homeWindow.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
-
-        // Landscape application
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState applicationWindow = createWindow(null, TYPE_BASE_APPLICATION, activity,
-                "applicationWindow");
-        activity.addWindow(applicationWindow);
-        activity.setOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
-
-        // Wallpaper
-        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
-                mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
-        final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
-                "wallpaperWindow");
-
-        // Make sure the landscape activity is on top and the display is in landscape
-        activity.moveFocusableActivityToTop("test");
-        mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
-                mDefaultDisplay.getRotation());
-
-        spyOn(mDefaultDisplay.mWallpaperController);
-        doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
-
-        // Start the recents animation
-        initializeRecentsAnimationController(mController, homeActivity);
-
-        mDefaultDisplay.mWallpaperController.adjustWallpaperWindows();
-
-        // Check preconditions
-        ArrayList<WallpaperWindowToken> wallpapers = new ArrayList<>(1);
-        mDefaultDisplay.forAllWallpaperWindows(wallpapers::add);
-
-        Truth.assertThat(wallpapers).hasSize(1);
-        Truth.assertThat(wallpapers.get(0).getTopChild()).isEqualTo(wallpaperWindow);
-
-        // Actual check
-        assertEquals(Configuration.ORIENTATION_PORTRAIT,
-                wallpapers.get(0).getConfiguration().orientation);
-
-        mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
-        // The transform state should keep because we expect to listen the signal from the
-        // transition executed by moving the task to front.
-        assertTrue(homeActivity.hasFixedRotationTransform());
-        assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity));
-
-        mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(
-                homeActivity.token);
-        // Wallpaper's transform state should be cleared with home.
-        assertFalse(homeActivity.hasFixedRotationTransform());
-        assertFalse(wallpaperWindowToken.hasFixedRotationTransform());
-    }
-
-    @Test
-    public void testIsAnimatingByRecents() {
-        final ActivityRecord homeActivity = createHomeActivity();
-        final Task rootTask = createTask(mDefaultDisplay);
-        final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
-        final Task leafTask = createTaskInRootTask(childTask, 0 /* userId */);
-        spyOn(leafTask);
-        doReturn(true).when(leafTask).isVisible();
-
-        initializeRecentsAnimationController(mController, homeActivity);
-
-        // Verify RecentsAnimationController will animate visible leaf task by default.
-        verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), any());
-        assertTrue(leafTask.isAnimatingByRecents());
-
-        // Make sure isAnimatingByRecents will also return true when it called by the parent task.
-        assertTrue(rootTask.isAnimatingByRecents());
-        assertTrue(childTask.isAnimatingByRecents());
-    }
-
-    @Test
-    public void testRestoreNavBarWhenEnteringRecents_expectAnimation() {
-        setupForShouldAttachNavBarDuringTransition();
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final ActivityRecord homeActivity = createHomeActivity();
-        initializeRecentsAnimationController(mController, homeActivity);
-
-        final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken;
-        final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
-
-        verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
-                eq(mDefaultDisplay.mDisplayId), eq(false));
-        verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
-        verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
-        assertTrue(mController.isNavigationBarAttachedToApp());
-
-        mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
-        verify(mController).restoreNavigationBarFromApp(eq(true));
-        verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
-                eq(mDefaultDisplay.mDisplayId), eq(true));
-        verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
-        assertFalse(mController.isNavigationBarAttachedToApp());
-        assertTrue(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM));
-    }
-
-    @Test
-    public void testRestoreNavBarWhenBackToApp_expectNoAnimation() {
-        setupForShouldAttachNavBarDuringTransition();
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final ActivityRecord homeActivity = createHomeActivity();
-        initializeRecentsAnimationController(mController, homeActivity);
-
-        final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken;
-        final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
-
-        verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
-                eq(mDefaultDisplay.mDisplayId), eq(false));
-        verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
-        verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
-        assertTrue(mController.isNavigationBarAttachedToApp());
-
-        final WindowContainer parent = navToken.getParent();
-
-        mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-        verify(mController).restoreNavigationBarFromApp(eq(false));
-        verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
-                eq(mDefaultDisplay.mDisplayId), eq(true));
-        verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
-        verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
-        assertFalse(mController.isNavigationBarAttachedToApp());
-        assertFalse(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM));
-    }
-
-    @Test
-    public void testAddTaskToTargets_expectAnimation() {
-        setupForShouldAttachNavBarDuringTransition();
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final ActivityRecord homeActivity = createHomeActivity();
-        initializeRecentsAnimationController(mController, homeActivity);
-
-        final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken;
-        final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
-
-        verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
-                eq(mDefaultDisplay.mDisplayId), eq(false));
-        verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
-        verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
-        assertTrue(mController.isNavigationBarAttachedToApp());
-
-        final WindowContainer parent = navToken.getParent();
-
-        mController.addTaskToTargets(createTask(mDefaultDisplay), (type, anim) -> {});
-        mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-        verify(mController).restoreNavigationBarFromApp(eq(true));
-        verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
-                eq(mDefaultDisplay.mDisplayId), eq(true));
-        verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
-        assertFalse(mController.isNavigationBarAttachedToApp());
-        assertTrue(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM));
-    }
-
-    @Test
-    public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
-        setupForShouldAttachNavBarDuringTransition();
-        AsyncRotationController mockController =
-                mock(AsyncRotationController.class);
-        doReturn(mockController).when(mDefaultDisplay).getAsyncRotationController();
-        final ActivityRecord homeActivity = createHomeActivity();
-        initializeRecentsAnimationController(mController, homeActivity);
-        assertFalse(mController.isNavigationBarAttachedToApp());
-    }
-
-    @Test
-    public void testAttachNavBarInSplitScreenMode() {
-        setupForShouldAttachNavBarDuringTransition();
-        TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
-        final ActivityRecord primary = createActivityRecordWithParentTask(
-                organizer.createTaskToPrimary(true));
-        final ActivityRecord secondary = createActivityRecordWithParentTask(
-                organizer.createTaskToSecondary(true));
-        final ActivityRecord homeActivity = createHomeActivity();
-        homeActivity.setVisibility(true);
-        initializeRecentsAnimationController(mController, homeActivity);
-
-        WindowState navWindow = mController.getNavigationBarWindow();
-        final WindowToken navToken = navWindow.mToken;
-        final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
-
-        verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
-                eq(mDefaultDisplay.mDisplayId), eq(false));
-        verify(navWindow).setSurfaceTranslationY(-secondary.getBounds().top);
-        verify(transaction).reparent(navToken.getSurfaceControl(), secondary.getSurfaceControl());
-        assertTrue(mController.isNavigationBarAttachedToApp());
-        reset(navWindow);
-
-        mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-        final WindowContainer parent = navToken.getParent();
-        verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
-                eq(mDefaultDisplay.mDisplayId), eq(true));
-        verify(navWindow).setSurfaceTranslationY(0);
-        verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
-        verify(mController).restoreNavigationBarFromApp(eq(false));
-        assertFalse(mController.isNavigationBarAttachedToApp());
-    }
-
-    @Test
-    public void testCleanupAnimation_expectExitAnimationDone() {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord homeActivity = createHomeActivity();
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-
-        initializeRecentsAnimationController(mController, homeActivity);
-        mController.startAnimation();
-
-        spyOn(win1);
-        spyOn(win1.mWinAnimator);
-        // Simulate when the window is exiting and cleanupAnimation invoked
-        // (e.g. screen off during RecentsAnimation animating), will expect the window receives
-        // onExitAnimationDone to destroy the surface when the removal is allowed.
-        win1.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
-        win1.mHasSurface = true;
-        win1.mAnimatingExit = true;
-        win1.mRemoveOnExit = true;
-        win1.mWindowRemovalAllowed = true;
-        mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
-        verify(win1).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), any());
-        verify(win1).onExitAnimationDone();
-        verify(win1).destroySurface(eq(false), eq(false));
-        assertFalse(win1.mAnimatingExit);
-        assertFalse(win1.mHasSurface);
-    }
-
-    @Test
-    public void testCancelForRotation_ReorderToTop() throws Exception {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-
-        mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
-        mController.setWillFinishToHome(true);
-        mController.cancelAnimationForDisplayChange();
-
-        verify(mMockRunner).onAnimationCanceled(any(), any());
-        verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false);
-    }
-
-    @Test
-    public void testCancelForRotation_ReorderToOriginalPosition() throws Exception {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-
-        mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
-        mController.setWillFinishToHome(false);
-        mController.cancelAnimationForDisplayChange();
-
-        verify(mMockRunner).onAnimationCanceled(any(), any());
-        verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_ORIGINAL_POSITION, false);
-    }
-
-    @Test
-    public void testCancelForStartHome() throws Exception {
-        mWm.setRecentsAnimationController(mController);
-        final ActivityRecord homeActivity = createHomeActivity();
-        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
-        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
-        activity.addWindow(win1);
-
-        initializeRecentsAnimationController(mController, homeActivity);
-        mController.setWillFinishToHome(true);
-
-        // Verify cancel is called with a snapshot and that we've created an overlay
-        spyOn(mWm.mTaskSnapshotController);
-        doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
-                anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
-        mController.cancelAnimationForHomeStart();
-        verify(mMockRunner).onAnimationCanceled(any(), any());
-
-        // Continue the animation (simulating a call to cleanupScreenshot())
-        mController.continueDeferredCancelAnimation();
-        verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false);
-
-        // Assume home was moved to front so will-be-top callback should not be called.
-        homeActivity.moveFocusableActivityToTop("test");
-        spyOn(mDefaultDisplay.mFixedRotationTransitionListener);
-        mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
-        verify(mDefaultDisplay.mFixedRotationTransitionListener, never()).notifyRecentsWillBeTop();
-    }
-
-    private ActivityRecord createHomeActivity() {
-        final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
-                .setParentTask(mRootHomeTask)
-                .setCreateTask(true)
-                .build();
-        // Avoid {@link RecentsAnimationController.TaskAnimationAdapter#createRemoteAnimationTarget}
-        // returning null when calling {@link RecentsAnimationController#createAppAnimations}.
-        homeActivity.setVisibility(true);
-        return homeActivity;
-    }
-
-    private void startRecentsInDifferentRotation(ActivityRecord recentsActivity) {
-        final DisplayContent displayContent = recentsActivity.mDisplayContent;
-        displayContent.setFixedRotationLaunchingApp(recentsActivity,
-                (displayContent.getRotation() + 1) % 4);
-        mController = new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
-                displayContent.getDisplayId());
-        initializeRecentsAnimationController(mController, recentsActivity);
-        assertTrue(recentsActivity.hasFixedRotationTransform());
-    }
-
-    private static void assertTopFixedRotationLaunchingAppCleared(ActivityRecord activity) {
-        assertFalse(activity.hasFixedRotationTransform());
-        assertFalse(activity.mDisplayContent.hasTopFixedRotationLaunchingApp());
-    }
-
-    private void setupForShouldAttachNavBarDuringTransition() {
-        final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar"));
-        mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
-        mWm.setRecentsAnimationController(mController);
-        doReturn(navBar).when(mController).getNavigationBarWindow();
-        final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy());
-        doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy();
-        doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition();
-    }
-
-    private static void initializeRecentsAnimationController(RecentsAnimationController controller,
-            ActivityRecord activity) {
-        controller.initialize(activity.getActivityType(), new SparseBooleanArray(), activity);
-    }
-
-    private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
-        verify(binder, atLeast(0)).asBinder();
-        verifyNoMoreInteractions(binder);
-    }
-}
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 f93ffb8..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,70 +727,6 @@
         }
     }
 
-    @Test
-    public void testNonAppTarget_notSendNavBar_controlledByRecents() throws Exception {
-        final RecentsAnimationController mockController =
-                mock(RecentsAnimationController.class);
-        doReturn(mockController).when(mWm).getRecentsAnimationController();
-        final int transit = TRANSIT_OLD_TASK_OPEN;
-        setupForNonAppTargetNavBar(transit, true);
-
-        final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
-                ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
-        verify(mMockRunner).onAnimationStart(eq(transit),
-                any(), any(), nonAppsCaptor.capture(), any());
-        for (int i = 0; i < nonAppsCaptor.getValue().length; i++) {
-            if (nonAppsCaptor.getValue()[0].windowType == TYPE_NAVIGATION_BAR) {
-                fail("Non-app animation target must not contain navbar");
-            }
-        }
-    }
-
-    @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 e019a41..7cb62c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1260,68 +1260,38 @@
 
     @Test
     public void testShouldSleepActivities() {
+        final Task task = new TaskBuilder(mSupervisor).build();
+        task.mDisplayContent = mock(DisplayContent.class);
         // When focused activity and keyguard is going away, we should not sleep regardless
         // of the display state, but keyguard-going-away should only take effects on default
-        // display since there is no keyguard on secondary displays (yet).
-        verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+        // display because the keyguard-going-away state of secondary displays are already the
+        // same as default display.
+        verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
                 true /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
-        verifyShouldSleepActivities(true /* focusedRootTask */, true /*keyguardGoingAway*/,
+        verifyShouldSleepActivities(task, true /* isVisibleTask */, true /* keyguardGoingAway */,
                 true /* displaySleeping */, false /* isDefaultDisplay */, true /* expected */);
 
         // When not the focused root task, defer to display sleeping state.
-        verifyShouldSleepActivities(false /* focusedRootTask */, true /*keyguardGoingAway*/,
+        verifyShouldSleepActivities(task, false /* isVisibleTask */, true /* keyguardGoingAway */,
                 true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
 
         // If keyguard is going away, defer to the display sleeping state.
-        verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+        verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
                 true /* displaySleeping */, true /* isDefaultDisplay */, true /* expected */);
-        verifyShouldSleepActivities(true /* focusedRootTask */, false /*keyguardGoingAway*/,
+        verifyShouldSleepActivities(task, true /* isVisibleTask */, false /* keyguardGoingAway */,
                 false /* displaySleeping */, true /* isDefaultDisplay */, false /* expected */);
     }
 
-    @Test
-    public void testRootTaskOrderChangedOnRemoveRootTask() {
-        final Task task = new TaskBuilder(mSupervisor).build();
-        RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener();
-        mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener);
-        try {
-            mDefaultTaskDisplayArea.removeRootTask(task);
-        } finally {
-            mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(listener);
-        }
-        assertTrue(listener.mChanged);
-    }
+    private static void verifyShouldSleepActivities(Task task, boolean isVisibleTask,
+            boolean keyguardGoingAway, boolean displaySleeping, boolean isDefaultDisplay,
+            boolean expected) {
+        final DisplayContent display = task.mDisplayContent;
+        display.isDefaultDisplay = isDefaultDisplay;
+        doReturn(keyguardGoingAway).when(display).isKeyguardGoingAway();
+        doReturn(displaySleeping).when(display).isSleeping();
+        doReturn(isVisibleTask).when(task).shouldBeVisible(null /* starting */);
 
-    @Test
-    public void testRootTaskOrderChangedOnAddPositionRootTask() {
-        final Task task = new TaskBuilder(mSupervisor).build();
-        mDefaultTaskDisplayArea.removeRootTask(task);
-
-        RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener();
-        mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener);
-        try {
-            task.mReparenting = true;
-            mDefaultTaskDisplayArea.addChild(task, 0);
-        } finally {
-            mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(listener);
-        }
-        assertTrue(listener.mChanged);
-    }
-
-    @Test
-    public void testRootTaskOrderChangedOnPositionRootTask() {
-        RootTaskOrderChangedListener listener = new RootTaskOrderChangedListener();
-        try {
-            final Task fullscreenRootTask1 = createTaskForShouldBeVisibleTest(
-                    mDefaultTaskDisplayArea, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
-                    true /* onTop */);
-            mDefaultTaskDisplayArea.registerRootTaskOrderChangedListener(listener);
-            mDefaultTaskDisplayArea.positionChildAt(POSITION_BOTTOM, fullscreenRootTask1,
-                    false /*includingParents*/);
-        } finally {
-            mDefaultTaskDisplayArea.unregisterRootTaskOrderChangedListener(listener);
-        }
-        assertTrue(listener.mChanged);
+        assertEquals(expected, task.shouldSleepActivities());
     }
 
     @Test
@@ -1450,35 +1420,4 @@
         verify(mSupervisor).startSpecificActivity(any(), eq(false) /* andResume */,
                 anyBoolean());
     }
-
-    private boolean isAssistantOnTop() {
-        return mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_assistantOnTopOfDream);
-    }
-
-    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());
-    }
-
-    private static class RootTaskOrderChangedListener
-            implements TaskDisplayArea.OnRootTaskOrderChangedListener {
-        public boolean mChanged = false;
-
-        @Override
-        public void onRootTaskOrderChanged(Task rootTask) {
-            mChanged = true;
-        }
-    }
 }
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/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 1e1055b..f743401 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -613,7 +613,7 @@
         assertFalse(mActivity.mDisplayContent.shouldImeAttachedToApp());
 
         // Recompute the natural configuration without resolving size compat configuration.
-        mActivity.clearSizeCompatMode();
+        mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
         mActivity.onConfigurationChanged(mTask.getConfiguration());
         // It should keep non-attachable because the resolved bounds will be computed according to
         // the aspect ratio that won't match its parent bounds.
@@ -706,7 +706,7 @@
                         / originalBounds.width()));
 
         // Recompute the natural configuration in the new display.
-        mActivity.clearSizeCompatMode();
+        mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
         mActivity.ensureActivityConfiguration();
         // Because the display cannot rotate, the portrait activity will fit the short side of
         // display with keeping portrait bounds [200, 0 - 700, 1000] in center.
@@ -957,12 +957,12 @@
                 .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
                 .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        assertTrue(activity.shouldCreateCompatDisplayInsets());
+        assertTrue(activity.shouldCreateAppCompatDisplayInsets());
 
         // The non-resizable activity should not be size compat because it is on a resizable task
         // in multi-window mode.
         mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
-        assertFalse(activity.shouldCreateCompatDisplayInsets());
+        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
         // Activity should not be sandboxed.
         assertMaxBoundsInheritDisplayAreaBounds();
 
@@ -971,7 +971,7 @@
         mTask.mDisplayContent.getDefaultTaskDisplayArea()
                 .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
         mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
-        assertFalse(activity.shouldCreateCompatDisplayInsets());
+        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
         // Activity should not be sandboxed.
         assertMaxBoundsInheritDisplayAreaBounds();
     }
@@ -986,7 +986,7 @@
         // Create an activity on the same task.
         final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */true,
                 RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        assertFalse(activity.shouldCreateCompatDisplayInsets());
+        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
     }
 
     @Test
@@ -999,7 +999,7 @@
         // Create an activity on the same task.
         final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                 RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        assertTrue(activity.shouldCreateCompatDisplayInsets());
+        assertTrue(activity.shouldCreateAppCompatDisplayInsets());
     }
 
     @Test
@@ -1012,7 +1012,7 @@
         // Create an activity on the same task.
         final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                 RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        assertFalse(activity.shouldCreateCompatDisplayInsets());
+        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
     }
 
     @Test
@@ -1026,7 +1026,7 @@
         // Create an activity on the same task.
         final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                 RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-        assertFalse(activity.shouldCreateCompatDisplayInsets());
+        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
     }
 
     @Test
@@ -1040,7 +1040,7 @@
         // Create an activity on the same task.
         final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                 RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        assertFalse(activity.shouldCreateCompatDisplayInsets());
+        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
     }
 
     @Test
@@ -1054,7 +1054,7 @@
         // Create an activity on the same task.
         final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */true,
                 RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        assertTrue(activity.shouldCreateCompatDisplayInsets());
+        assertTrue(activity.shouldCreateAppCompatDisplayInsets());
     }
 
     @Test
@@ -1068,7 +1068,7 @@
         // Create an activity on the same task.
         final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */true,
                 RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-        assertTrue(activity.shouldCreateCompatDisplayInsets());
+        assertTrue(activity.shouldCreateAppCompatDisplayInsets());
     }
 
     @Test
@@ -1090,7 +1090,7 @@
         doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
                 .when(activity.mAppCompatController.getAppCompatAspectRatioOverrides())
                 .getUserMinAspectRatioOverrideCode();
-        assertFalse(activity.shouldCreateCompatDisplayInsets());
+        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
     }
 
     @Test
@@ -1110,7 +1110,7 @@
         doReturn(true).when(
                 activity.mAppCompatController.getAppCompatAspectRatioOverrides())
                     .isSystemOverrideToFullscreenEnabled();
-        assertFalse(activity.shouldCreateCompatDisplayInsets());
+        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
     }
 
     @Test
@@ -1482,7 +1482,7 @@
 
         // After changing the orientation to portrait the override should be applied.
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        activity.clearSizeCompatMode();
+        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
 
         // The per-package override forces the activity into a 3:2 aspect ratio
         assertEquals(1200, activity.getBounds().height());
@@ -1511,7 +1511,7 @@
 
         // After changing the orientation to portrait the override should be applied.
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        activity.clearSizeCompatMode();
+        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
 
         // The per-package override forces the activity into a 3:2 aspect ratio
         assertEquals(1200, activity.getBounds().height());
@@ -1538,7 +1538,7 @@
 
         // After changing the orientation to landscape the override shouldn't be applied.
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-        activity.clearSizeCompatMode();
+        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
 
         // The per-package override should have no effect
         assertEquals(1200, activity.getBounds().height());
@@ -3054,7 +3054,10 @@
                 false /* deferPause */);
 
         // App still in size compat, and the bounds don't change.
-        verify(mActivity, never()).clearSizeCompatMode();
+        final AppCompatSizeCompatModePolicy scmPolicy = mActivity.mAppCompatController
+                .getAppCompatSizeCompatModePolicy();
+        spyOn(scmPolicy);
+        verify(scmPolicy, never()).clearSizeCompatMode();
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertDownScaled();
@@ -3256,7 +3259,7 @@
         // Activity max bounds are sandboxed since app may enter size compat mode.
         assertActivityMaxBoundsSandboxed();
         assertFalse(mActivity.inSizeCompatMode());
-        assertTrue(mActivity.shouldCreateCompatDisplayInsets());
+        assertTrue(mActivity.shouldCreateAppCompatDisplayInsets());
 
         // Resize display to half the width.
         resizeDisplay(mActivity.getDisplayContent(), 500, 1000);
@@ -3896,7 +3899,7 @@
 
     private void recomputeNaturalConfigurationOfUnresizableActivity() {
         // Recompute the natural configuration of the non-resizable activity and the split screen.
-        mActivity.clearSizeCompatMode();
+        mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
 
         // Draw letterbox.
         mActivity.setVisible(false);
@@ -4102,8 +4105,8 @@
         // To force config to update again but with the same landscape orientation.
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
 
-        assertTrue(activity.shouldCreateCompatDisplayInsets());
-        assertNotNull(activity.getCompatDisplayInsets());
+        assertTrue(activity.shouldCreateAppCompatDisplayInsets());
+        assertNotNull(activity.getAppCompatDisplayInsets());
         // Activity is not letterboxed for fixed orientation because orientation is respected
         // with insets, and should not be in size compat mode
         assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
@@ -4827,7 +4830,7 @@
         assertEquals(origDensity, mActivity.getConfiguration().densityDpi);
 
         // Activity should exit size compat with new density.
-        mActivity.clearSizeCompatMode();
+        mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
 
         assertFitted();
         assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
@@ -5013,7 +5016,7 @@
             activity.setRequestedOrientation(screenOrientation);
         }
         // Make sure to use the provided configuration to construct the size compat fields.
-        activity.clearSizeCompatMode();
+        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
         activity.ensureActivityConfiguration();
         // Make sure the display configuration reflects the change of activity.
         if (activity.mDisplayContent.updateOrientation()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 6c8a7ac..9967cce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -23,7 +23,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -37,7 +36,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Builder;
 import android.view.SurfaceControl.Transaction;
-import android.view.SurfaceSession;
 
 import androidx.test.filters.SmallTest;
 
@@ -70,7 +68,6 @@
     @Mock AnimationAdapter mSpec2;
     @Mock Transaction mTransaction;
 
-    private SurfaceSession mSession = new SurfaceSession();
     private MyAnimatable mAnimatable;
     private MyAnimatable mAnimatable2;
     private DeferFinishAnimatable mDeferFinishAnimatable;
@@ -79,9 +76,9 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mAnimatable = new MyAnimatable(mWm, mSession, mTransaction);
-        mAnimatable2 = new MyAnimatable(mWm, mSession, mTransaction);
-        mDeferFinishAnimatable = new DeferFinishAnimatable(mWm, mSession, mTransaction);
+        mAnimatable = new MyAnimatable(mWm, mTransaction);
+        mAnimatable2 = new MyAnimatable(mWm, mTransaction);
+        mDeferFinishAnimatable = new DeferFinishAnimatable(mWm, mTransaction);
     }
 
     @After
@@ -89,25 +86,23 @@
         mAnimatable = null;
         mAnimatable2 = null;
         mDeferFinishAnimatable = null;
-        mSession.kill();
-        mSession = null;
     }
 
     @Test
     public void testRunAnimation() {
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
-                ANIMATION_TYPE_RECENTS);
+                ANIMATION_TYPE_APP_TRANSITION);
         final ArgumentCaptor<OnAnimationFinishedCallback> callbackCaptor = ArgumentCaptor.forClass(
                 OnAnimationFinishedCallback.class);
         assertAnimating(mAnimatable);
         verify(mTransaction).reparent(eq(mAnimatable.mSurface), eq(mAnimatable.mLeash));
-        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_RECENTS),
+        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION),
                 callbackCaptor.capture());
 
-        callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_RECENTS, mSpec);
+        callbackCaptor.getValue().onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, mSpec);
         assertNotAnimating(mAnimatable);
         assertTrue(mAnimatable.mFinishedCallbackCalled);
-        assertEquals(ANIMATION_TYPE_RECENTS, mAnimatable.mFinishedAnimationType);
+        assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
         verify(mTransaction).remove(eq(mAnimatable.mLeash));
         // TODO: Verify reparenting once we use mPendingTransaction to reparent it back
     }
@@ -314,7 +309,6 @@
 
     private static class MyAnimatable implements Animatable {
 
-        private final SurfaceSession mSession;
         private final Transaction mTransaction;
         final SurfaceControl mParent;
         final SurfaceControl mSurface;
@@ -323,13 +317,12 @@
         boolean mFinishedCallbackCalled;
         @AnimationType int mFinishedAnimationType;
 
-        MyAnimatable(WindowManagerService wm, SurfaceSession session, Transaction transaction) {
-            mSession = session;
+        MyAnimatable(WindowManagerService wm, Transaction transaction) {
             mTransaction = transaction;
-            mParent = wm.makeSurfaceBuilder(mSession)
+            mParent = wm.makeSurfaceBuilder()
                     .setName("test surface parent")
                     .build();
-            mSurface = wm.makeSurfaceBuilder(mSession)
+            mSurface = wm.makeSurfaceBuilder()
                     .setName("test surface")
                     .build();
             mFinishedCallbackCalled = false;
@@ -361,7 +354,7 @@
 
         @Override
         public Builder makeAnimationLeash() {
-            return new Builder(mSession) {
+            return new Builder() {
 
                 @Override
                 public SurfaceControl build() {
@@ -407,9 +400,8 @@
 
         Runnable mEndDeferFinishCallback;
 
-        DeferFinishAnimatable(WindowManagerService wm, SurfaceSession session,
-                Transaction transaction) {
-            super(wm, session, transaction);
+        DeferFinishAnimatable(WindowManagerService wm, Transaction transaction) {
+            super(wm, transaction);
         }
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 1e39f0b..ae722807 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -394,7 +394,7 @@
         mWmService = WindowManagerService.main(
                 mContext, mImService, false, wmPolicy, mAtmService,
                 testDisplayWindowSettingsProvider, StubTransaction::new,
-                (unused) -> new MockSurfaceControlBuilder());
+                MockSurfaceControlBuilder::new);
         spyOn(mWmService);
         spyOn(mWmService.mRoot);
         // Invoked during {@link ActivityStack} creation.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 6fd5faf..b595383 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -34,6 +34,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -50,6 +51,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
@@ -839,4 +841,16 @@
                 0 /* launchFlags */, pinnedTask /* candidateTask */);
         assertNull(actualRootTask);
     }
+
+    @Test
+    public void testMovedRootTaskToFront() {
+        final TaskDisplayArea tda = mDefaultDisplay.getDefaultTaskDisplayArea();
+        final Task rootTask = createTask(tda, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */, true /* createActivity */, true /* twoLevelTask */);
+        final Task leafTask = rootTask.getTopLeafTask();
+
+        clearInvocations(tda);
+        tda.onTaskMoved(rootTask, true /* toTop */, false /* toBottom */);
+        verify(tda).onLeafTaskMoved(eq(leafTask), anyBoolean(), anyBoolean());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index d013053..0cd036f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -90,6 +90,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.view.RemoteAnimationDefinition;
 import android.view.SurfaceControl;
 import android.window.IRemoteTransition;
 import android.window.ITaskFragmentOrganizer;
@@ -139,6 +140,7 @@
     private IBinder mFragmentToken;
     private WindowContainerTransaction mTransaction;
     private WindowContainerToken mFragmentWindowToken;
+    private RemoteAnimationDefinition mDefinition;
     private IBinder mErrorToken;
     private Rect mTaskFragBounds;
 
@@ -167,6 +169,7 @@
         mTransaction = new WindowContainerTransaction();
         mTransaction.setTaskFragmentOrganizer(mIOrganizer);
         mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
+        mDefinition = new RemoteAnimationDefinition();
         mErrorToken = new Binder();
         final Rect displayBounds = mDisplayContent.getBounds();
         mTaskFragBounds = new Rect(displayBounds.left, displayBounds.top, displayBounds.centerX(),
@@ -576,6 +579,17 @@
     }
 
     @Test
+    public void testRegisterRemoteAnimations() {
+        mController.registerRemoteAnimations(mIOrganizer, mDefinition);
+
+        assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
+
+        mController.unregisterRemoteAnimations(mIOrganizer);
+
+        assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
+    }
+
+    @Test
     public void testApplyTransaction_disallowRemoteTransitionForNonSystemOrganizer() {
         mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
         mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
@@ -2121,8 +2135,8 @@
     private static void setupMockParent(TaskFragment taskFragment, Task mockParent) {
         doReturn(mockParent).when(taskFragment).getTask();
         doReturn(new TaskFragmentParentInfo(
-                new Configuration(), DEFAULT_DISPLAY, true, true, null /* decorSurface */))
-                .when(mockParent).getTaskFragmentParentInfo();
+                new Configuration(), DEFAULT_DISPLAY, mockParent.mTaskId, true, true,
+                null /* decorSurface */)).when(mockParent).getTaskFragmentParentInfo();
 
         // Task needs to be visible
         mockParent.lastActiveTime = 100;
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 0a592f2..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.
@@ -867,8 +866,8 @@
         // Without limiting to be inside the parent bounds, the out screen size should keep relative
         // to the input bounds.
         final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
-        final ActivityRecord.CompatDisplayInsets compatInsets =
-                new ActivityRecord.CompatDisplayInsets(
+        final AppCompatDisplayInsets compatInsets =
+                new AppCompatDisplayInsets(
                         display, activity, /* letterboxedContainerBounds */ null,
                         /* useOverrideInsets */ false);
         final TaskFragment.ConfigOverrideHint overrideHint = new TaskFragment.ConfigOverrideHint();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index d62c626..eebb487 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -60,7 +60,7 @@
 
     @Override
     public int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
-            int[] outAppOp) {
+            int[] outAppOp, int displayId) {
         return 0;
     }
 
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/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index 29f6360..7a440e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -322,9 +322,12 @@
                     a.checkTopActivityInSizeCompatMode(/* inScm */ true);
 
                     ta.launchTransparentActivityInTask();
-                    a.assertNotNullOnTopActivity(ActivityRecord::getCompatDisplayInsets);
-                    a.applyToTopActivity(ActivityRecord::clearSizeCompatMode);
-                    a.assertNullOnTopActivity(ActivityRecord::getCompatDisplayInsets);
+                    a.assertNotNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
+                    a.applyToTopActivity((top) -> {
+                        top.mAppCompatController.getAppCompatSizeCompatModePolicy()
+                                .clearSizeCompatMode();
+                    });
+                    a.assertNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
                 });
             });
         });
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 c65b76e..9602ae2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -37,7 +37,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -276,9 +275,8 @@
         final DisplayContent dc = mDisplayContent;
         final WindowState homeWin = createWallpaperTargetWindow(dc);
         final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, "app");
-        final RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
-        doReturn(true).when(recentsController).isWallpaperVisible(eq(appWin));
-        mWm.setRecentsAnimationController(recentsController);
+        appWin.mAttrs.flags |= FLAG_SHOW_WALLPAPER;
+        makeWindowVisible(appWin);
 
         dc.mWallpaperController.adjustWallpaperWindows();
         assertEquals(appWin, dc.mWallpaperController.getWallpaperTarget());
@@ -354,46 +352,6 @@
     }
 
     @Test
-    public void testFixedRotationRecentsAnimatingTask() {
-        final WindowState wallpaperWindow = createWallpaperWindow(mDisplayContent);
-        final WallpaperWindowToken wallpaperToken = wallpaperWindow.mToken.asWallpaperToken();
-        final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, "app");
-        makeWindowVisible(appWin);
-        final ActivityRecord r = appWin.mActivityRecord;
-        final RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
-        doReturn(true).when(recentsController).isWallpaperVisible(eq(appWin));
-        mWm.setRecentsAnimationController(recentsController);
-
-        r.applyFixedRotationTransform(mDisplayContent.getDisplayInfo(),
-                mDisplayContent.mDisplayFrames, mDisplayContent.getConfiguration());
-        // Invisible requested activity should not share its rotation transform.
-        r.setVisibleRequested(false);
-        mDisplayContent.mWallpaperController.adjustWallpaperWindows();
-        assertFalse(wallpaperToken.hasFixedRotationTransform());
-
-        // Wallpaper should link the transform of its target.
-        r.setVisibleRequested(true);
-        mDisplayContent.mWallpaperController.adjustWallpaperWindows();
-        assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
-        assertTrue(r.hasFixedRotationTransform());
-        assertTrue(wallpaperToken.hasFixedRotationTransform());
-
-        // The case with shell transition.
-        registerTestTransitionPlayer();
-        final Transition t = r.mTransitionController.createTransition(TRANSIT_OPEN);
-        final ActivityRecord recents = mock(ActivityRecord.class);
-        t.collect(r.getTask());
-        r.mTransitionController.setTransientLaunch(recents, r.getTask());
-        // The activity in restore-below task should not be the target if keyguard is not locked.
-        mDisplayContent.mWallpaperController.adjustWallpaperWindows();
-        assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
-        // The activity in restore-below task should not be the target if keyguard is occluded.
-        doReturn(true).when(mDisplayContent).isKeyguardLocked();
-        mDisplayContent.mWallpaperController.adjustWallpaperWindows();
-        assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
-    }
-
-    @Test
     public void testWallpaperReportConfigChange() {
         final WindowState wallpaperWindow = createWallpaperWindow(mDisplayContent);
         createWallpaperTargetWindow(mDisplayContent);
@@ -449,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/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 39640fb..916c237 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -1189,7 +1189,7 @@
         final int displayId = mDisplayContent.mDisplayId;
         // Use real surface, so ViewportWindow's BlastBufferQueue can be created.
         final ArrayList<SurfaceControl> surfaceControls = new ArrayList<>();
-        mWm.mSurfaceControlFactory = s -> new SurfaceControl.Builder() {
+        mWm.mSurfaceControlFactory = () -> new SurfaceControl.Builder() {
             @Override
             public SurfaceControl build() {
                 final SurfaceControl sc = super.build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 0cb22ad..9bad2ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -44,7 +44,6 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
@@ -197,21 +196,6 @@
     }
 
     @Test
-    public void testSetRunningBothAnimations() {
-        mWpc.setRunningRemoteAnimation(true);
-        mWpc.setRunningRecentsAnimation(true);
-
-        mWpc.setRunningRecentsAnimation(false);
-        mWpc.setRunningRemoteAnimation(false);
-        waitHandlerIdle(mAtm.mH);
-
-        InOrder orderVerifier = Mockito.inOrder(mMockListener);
-        orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(true));
-        orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(false));
-        orderVerifier.verifyNoMoreInteractions();
-    }
-
-    @Test
     public void testConfigurationForSecondaryScreenDisplayArea() {
         // By default, the process should not listen to any display area.
         assertNull(mWpc.getDisplayArea());
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/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 973ab84..4f60106 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -40,25 +40,17 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.WindowStateAnimator.PRESERVED_SURFACE_LAYER;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
-import android.util.SparseBooleanArray;
-import android.view.IRecentsAnimationRunner;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 import android.window.ScreenCapture;
 
 import androidx.test.filters.SmallTest;
@@ -70,7 +62,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
-import java.util.function.Function;
+import java.util.function.Supplier;
 
 /**
  * Tests for the {@link DisplayContent#assignChildLayers(SurfaceControl.Transaction)} method.
@@ -133,8 +125,7 @@
         private LayerRecordingTransaction mTransaction;
         private SurfaceControl mPendingParent;
 
-        HierarchyRecorder(SurfaceSession s, LayerRecordingTransaction transaction) {
-            super(s);
+        HierarchyRecorder(LayerRecordingTransaction transaction) {
             mTransaction = transaction;
         }
 
@@ -153,8 +144,8 @@
         }
     }
 
-    private static class HierarchyRecordingBuilderFactory implements Function<SurfaceSession,
-            SurfaceControl.Builder> {
+    private static class HierarchyRecordingBuilderFactory
+            implements Supplier<SurfaceControl.Builder> {
         private LayerRecordingTransaction mTransaction;
 
         HierarchyRecordingBuilderFactory(LayerRecordingTransaction transaction) {
@@ -162,9 +153,8 @@
         }
 
         @Override
-        public SurfaceControl.Builder apply(SurfaceSession s) {
-            final LayerRecordingTransaction transaction = mTransaction;
-            return new HierarchyRecorder(s, transaction);
+        public SurfaceControl.Builder get() {
+            return new HierarchyRecorder(mTransaction);
         }
     }
 
@@ -410,30 +400,6 @@
     }
 
     @Test
-    public void testAssignWindowLayers_ForImeOnAppWithRecentsAnimating() {
-        final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
-                mAppWindow.mActivityRecord, "imeAppTarget");
-        mDisplayContent.setImeInputTarget(imeAppTarget);
-        mDisplayContent.setImeLayeringTarget(imeAppTarget);
-        mDisplayContent.setImeControlTarget(imeAppTarget);
-        mDisplayContent.updateImeParent();
-
-        // Simulate the ime layering target task is animating with recents animation.
-        final Task imeAppTargetTask = imeAppTarget.getTask();
-        final SurfaceAnimator imeTargetTaskAnimator = imeAppTargetTask.mSurfaceAnimator;
-        spyOn(imeTargetTaskAnimator);
-        doReturn(ANIMATION_TYPE_RECENTS).when(imeTargetTaskAnimator).getAnimationType();
-        doReturn(true).when(imeTargetTaskAnimator).isAnimating();
-
-        mDisplayContent.assignChildLayers(mTransaction);
-
-        // Ime should on top of the application window when in recents animation and keep
-        // attached on app.
-        assertTrue(mDisplayContent.shouldImeAttachedToApp());
-        assertWindowHigher(mImeWindow, imeAppTarget);
-    }
-
-    @Test
     public void testAssignWindowLayers_ForImeOnPopupImeLayeringTarget() {
         final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
                 mAppWindow.mActivityRecord, "imeAppTarget");
@@ -493,43 +459,6 @@
     }
 
     @Test
-    public void testAttachNavBarWhenEnteringRecents_expectNavBarHigherThanIme() {
-        // create RecentsAnimationController
-        IRecentsAnimationRunner mockRunner = mock(IRecentsAnimationRunner.class);
-        when(mockRunner.asBinder()).thenReturn(new Binder());
-        final int displayId = mDisplayContent.getDisplayId();
-        RecentsAnimationController controller = new RecentsAnimationController(
-                mWm, mockRunner, null, displayId);
-        spyOn(controller);
-        doReturn(mNavBarWindow).when(controller).getNavigationBarWindow();
-        mWm.setRecentsAnimationController(controller);
-
-        // set ime visible
-        spyOn(mDisplayContent.mInputMethodWindow);
-        doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible();
-
-        DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
-        spyOn(policy);
-        doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition();
-
-        // create home activity
-        Task rootHomeTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
-        final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
-                .setParentTask(rootHomeTask)
-                .setCreateTask(true)
-                .build();
-        homeActivity.setVisibility(true);
-
-        // start recent animation
-        controller.initialize(homeActivity.getActivityType(), new SparseBooleanArray(),
-                homeActivity);
-
-        mDisplayContent.assignChildLayers(mTransaction);
-        assertZOrderGreaterThan(mTransaction, mNavBarWindow.mToken.getSurfaceControl(),
-                mDisplayContent.getImeContainer().getSurfaceControl());
-    }
-
-    @Test
     public void testPopupWindowAndParentIsImeTarget_expectHigherThanIme_inMultiWindow() {
         // Simulate the app window is in multi windowing mode and being IME target
         mAppWindow.getConfiguration().windowConfiguration.setWindowingMode(
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/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index feea55b..a678147 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -33,7 +33,6 @@
 import android.widget.Toast;
 
 import com.android.internal.telephony.TelephonyPermissions;
-import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 /**
@@ -312,18 +311,10 @@
         // This avoid breaking legacy code that rely on public-facing APIs to access cell location,
         // and it doesn't create an info leak risk because the cell location is stored in the phone
         // process anyway, and the system server already has location access.
-        if (Flags.supportPhoneUidCheckForMultiuser()) {
-            if (TelephonyPermissions.isSystemOrPhone(query.callingUid)
-                    || UserHandle.isSameApp(query.callingUid, Process.NETWORK_STACK_UID)
-                    || UserHandle.isSameApp(query.callingUid, Process.ROOT_UID)) {
-                return LocationPermissionResult.ALLOWED;
-            }
-        } else {
-            if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID
-                    || query.callingUid == Process.NETWORK_STACK_UID
-                    || query.callingUid == Process.ROOT_UID) {
-                return LocationPermissionResult.ALLOWED;
-            }
+        if (TelephonyPermissions.isSystemOrPhone(query.callingUid)
+                || UserHandle.isSameApp(query.callingUid, Process.NETWORK_STACK_UID)
+                || UserHandle.isSameApp(query.callingUid, Process.ROOT_UID)) {
+            return LocationPermissionResult.ALLOWED;
         }
 
         // Check the system-wide requirements. If the location main switch is off and the caller is
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 7ed26fb..b80610a 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -35,7 +35,6 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.flags.Flags;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -934,12 +933,8 @@
      * as system user or not.
      */
     public static boolean isSystemOrPhone(int uid) {
-        if (Flags.supportPhoneUidCheckForMultiuser()) {
-            return UserHandle.isSameApp(uid, Process.SYSTEM_UID) || UserHandle.isSameApp(uid,
-                    Process.PHONE_UID);
-        } else {
-            return uid == Process.SYSTEM_UID || uid == Process.PHONE_UID;
-        }
+        return UserHandle.isSameApp(uid, Process.SYSTEM_UID) || UserHandle.isSameApp(uid,
+                Process.PHONE_UID);
     }
 
     /**
@@ -947,11 +942,7 @@
      * as system user or not.
      */
     public static boolean isRootOrShell(int uid) {
-        if (Flags.supportPhoneUidCheckForMultiuser()) {
-            return UserHandle.isSameApp(uid, Process.ROOT_UID) || UserHandle.isSameApp(uid,
-                    Process.SHELL_UID);
-        } else {
-            return uid == Process.ROOT_UID || uid == Process.SHELL_UID;
-        }
+        return UserHandle.isSameApp(uid, Process.ROOT_UID) || UserHandle.isSameApp(uid,
+                Process.SHELL_UID);
     }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index afd5720..aca0941 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.
@@ -10002,6 +10014,19 @@
     public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING =
             "satellite_nidd_apn_name_string";
 
+    /**
+     * Default value {@code true}, meaning when an emergency call request comes in, if the device is
+     * in emergency satellite mode but hasn't sent the first satellite datagram, then exits
+     * satellite mode to allow the emergency call to go through.
+     *
+     * If {@code false}, the emergency call is always blocked if device is in emergency satellite
+     * mode. Note if device is NOT in emergency satellite mode, emergency call is always allowed.
+     *
+     * @hide
+     */
+    public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL =
+            "satellite_roaming_turn_off_session_for_emergency_call_bool";
+
     /** @hide */
     @IntDef({
             CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC,
@@ -10073,8 +10098,36 @@
      * The default value is 30 seconds.
      */
     @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";
+    public static final String KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT =
+            "satellite_roaming_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_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT =
+            "satellite_roaming_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_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT =
+            "satellite_roaming_esos_inactivity_timeout_sec_int";
 
     /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
@@ -11234,12 +11287,16 @@
         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.putBoolean(KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL, true);
         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_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT, 30);
+        sDefaults.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT, 180);
+        sDefaults.putInt(KEY_SATELLITE_ROAMING_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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index dbe4f27..3ff1e2c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -23,6 +23,7 @@
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.Manifest;
+import android.annotation.BoolRes;
 import android.annotation.BytesLong;
 import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
@@ -132,6 +133,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
 import java.io.IOException;
@@ -1777,8 +1779,8 @@
 
     /**
      * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE}
-     * to indicate user to decide whether current SIM should be preferred for all
-     * data / voice / sms. {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
+     * to indicate the current SIM should be preferred for all data / voice / sms.
+     * {@link #EXTRA_SUBSCRIPTION_ID} will specified to indicate
      * which subscription should be the default subscription.
      * @hide
      */
@@ -6885,6 +6887,26 @@
         }
     }
 
+    // Suppressing AndroidFrameworkCompatChange because we're querying vendor
+    // partition SDK level, not application's target SDK version.
+    @SuppressWarnings("AndroidFrameworkCompatChange")
+    private boolean hasCapability(@NonNull String feature, @BoolRes int legacySetting) {
+        if (mContext == null) return true;
+
+        if (mContext.getPackageManager().hasSystemFeature(feature)) return true;
+
+        // Check SDK version of the vendor partition.
+        final int vendorApiLevel = SystemProperties.getInt(
+                "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
+        // Devices shipped with 2024Q2 or later are required to declare FEATURE_TELEPHONY_*
+        // for individual sub-features (calling, messaging, data), so there's no need to check
+        // the legacy setting.
+        if (vendorApiLevel < Build.VENDOR_API_2024_Q2) {
+            return mContext.getResources().getBoolean(legacySetting);
+        }
+        return false;
+    }
+
     // TODO(b/316183370): replace all @code with @link in javadoc after feature is released
     /**
      * @return true if the current device is "voice capable".
@@ -6904,11 +6926,9 @@
      * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for
      * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details.
      */
-    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     @Deprecated
     public boolean isVoiceCapable() {
-        if (mContext == null) return true;
-        return mContext.getResources().getBoolean(
+        return hasCapability(PackageManager.FEATURE_TELEPHONY_CALLING,
                 com.android.internal.R.bool.config_voice_capable);
     }
 
@@ -6931,7 +6951,6 @@
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
-    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public boolean isDeviceVoiceCapable() {
         return isVoiceCapable();
@@ -6950,10 +6969,9 @@
      * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for
      * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details.
      */
-    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
+    @Deprecated
     public boolean isSmsCapable() {
-        if (mContext == null) return true;
-        return mContext.getResources().getBoolean(
+        return hasCapability(PackageManager.FEATURE_TELEPHONY_MESSAGING,
                 com.android.internal.R.bool.config_sms_capable);
     }
 
@@ -6973,7 +6991,6 @@
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
-    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
     public boolean isDeviceSmsCapable() {
         return isSmsCapable();
@@ -9975,6 +9992,7 @@
             ALLOWED_NETWORK_TYPES_REASON_POWER,
             ALLOWED_NETWORK_TYPES_REASON_CARRIER,
             ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
+            ALLOWED_NETWORK_TYPES_REASON_TEST,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AllowedNetworkTypesReason {
@@ -10013,6 +10031,14 @@
     public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3;
 
     /**
+     * To indicate allowed network type change is requested by Testing purpose, should be default to
+     * {@link #getAllNetworkTypesBitmask} when done testing.
+     *
+     * @hide
+     */
+    public static final int ALLOWED_NETWORK_TYPES_REASON_TEST = 4;
+
+    /**
      * Set the allowed network types of the device and provide the reason triggering the allowed
      * network change.
      * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
@@ -10118,6 +10144,8 @@
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
                 return true;
+            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_TEST:
+                return TelephonyUtils.IS_DEBUGGABLE;
         }
         return false;
     }
@@ -13928,7 +13956,10 @@
      *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_BASIC_PHONE_STATE,
+            android.Manifest.permission.READ_PHONE_STATE
+    })
     public void getCarrierRestrictionStatus(@NonNull Executor executor,
             @NonNull @CarrierRestrictionStatus
                     Consumer<Integer> resultListener) {
@@ -14527,10 +14558,8 @@
      * data connections over the telephony network.
      * <p>
      */
-    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public boolean isDataCapable() {
-        if (mContext == null) return true;
-        return mContext.getResources().getBoolean(
+        return hasCapability(PackageManager.FEATURE_TELEPHONY_DATA,
                 com.android.internal.R.bool.config_mobile_data_capable);
     }
 
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 6ef953c..3944b8e 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -412,6 +412,14 @@
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27;
 
+    /**
+     * Disabling satellite is in progress.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int SATELLITE_RESULT_DISABLE_IN_PROGRESS = 28;
+
     /** @hide */
     @IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
             SATELLITE_RESULT_SUCCESS,
@@ -441,7 +449,8 @@
             SATELLITE_RESULT_MODEM_TIMEOUT,
             SATELLITE_RESULT_LOCATION_DISABLED,
             SATELLITE_RESULT_LOCATION_NOT_AVAILABLE,
-            SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS
+            SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS,
+            SATELLITE_RESULT_DISABLE_IN_PROGRESS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteResult {}
@@ -546,6 +555,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,
@@ -582,7 +601,7 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.requestSatelliteEnabled(mSubId, attributes.isEnabled(),
+                telephony.requestSatelliteEnabled(attributes.isEnabled(),
                         attributes.isDemoMode(), attributes.isEmergencyMode(), errorCallback);
             } else {
                 Rlog.e(TAG, "requestEnabled() invalid telephony");
@@ -640,7 +659,7 @@
                         }
                     }
                 };
-                telephony.requestIsSatelliteEnabled(mSubId, receiver);
+                telephony.requestIsSatelliteEnabled(receiver);
             } else {
                 loge("requestIsEnabled() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -697,7 +716,7 @@
                         }
                     }
                 };
-                telephony.requestIsDemoModeEnabled(mSubId, receiver);
+                telephony.requestIsDemoModeEnabled(receiver);
             } else {
                 loge("requestIsDemoModeEnabled() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -754,7 +773,7 @@
                         }
                     }
                 };
-                telephony.requestIsEmergencyModeEnabled(mSubId, receiver);
+                telephony.requestIsEmergencyModeEnabled(receiver);
             } else {
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
@@ -811,7 +830,7 @@
                         }
                     }
                 };
-                telephony.requestIsSatelliteSupported(mSubId, receiver);
+                telephony.requestIsSatelliteSupported(receiver);
             } else {
                 loge("requestIsSupported() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -868,7 +887,7 @@
                         }
                     }
                 };
-                telephony.requestSatelliteCapabilities(mSubId, receiver);
+                telephony.requestSatelliteCapabilities(receiver);
             } else {
                 loge("requestCapabilities() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -1194,8 +1213,7 @@
                             }
                         };
                 sSatelliteTransmissionUpdateCallbackMap.put(callback, internalCallback);
-                telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
-                        internalCallback);
+                telephony.startSatelliteTransmissionUpdates(errorCallback, internalCallback);
             } else {
                 loge("startTransmissionUpdates() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
@@ -1245,8 +1263,7 @@
                                     () -> resultListener.accept(result)));
                         }
                     };
-                    telephony.stopSatelliteTransmissionUpdates(mSubId, errorCallback,
-                            internalCallback);
+                    telephony.stopSatelliteTransmissionUpdates(errorCallback, internalCallback);
                     // TODO: Notify SmsHandler that pointing UI stopped
                 } else {
                     loge("stopSatelliteTransmissionUpdates: No internal callback.");
@@ -1302,7 +1319,7 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
+                cancelRemote = telephony.provisionSatelliteService(token, provisionData,
                         errorCallback);
             } else {
                 loge("provisionService() invalid telephony");
@@ -1354,7 +1371,7 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
+                telephony.deprovisionSatelliteService(token, errorCallback);
             } else {
                 loge("deprovisionService() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
@@ -1409,8 +1426,7 @@
                             }
                         };
                 sSatelliteProvisionStateCallbackMap.put(callback, internalCallback);
-                return telephony.registerForSatelliteProvisionStateChanged(
-                        mSubId, internalCallback);
+                return telephony.registerForSatelliteProvisionStateChanged(internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1443,7 +1459,7 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 if (internalCallback != null) {
-                    telephony.unregisterForSatelliteProvisionStateChanged(mSubId, internalCallback);
+                    telephony.unregisterForSatelliteProvisionStateChanged(internalCallback);
                 } else {
                     loge("unregisterForProvisionStateChanged: No internal callback.");
                 }
@@ -1500,7 +1516,7 @@
                         }
                     }
                 };
-                telephony.requestIsSatelliteProvisioned(mSubId, receiver);
+                telephony.requestIsSatelliteProvisioned(receiver);
             } else {
                 loge("requestIsProvisioned() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -1550,7 +1566,7 @@
                     }
                 };
                 sSatelliteModemStateCallbackMap.put(callback, internalCallback);
-                return telephony.registerForSatelliteModemStateChanged(mSubId, internalCallback);
+                return telephony.registerForSatelliteModemStateChanged(internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1583,7 +1599,7 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 if (internalCallback != null) {
-                    telephony.unregisterForModemStateChanged(mSubId, internalCallback);
+                    telephony.unregisterForModemStateChanged(internalCallback);
                 } else {
                     loge("unregisterForModemStateChanged: No internal callback.");
                 }
@@ -1646,7 +1662,7 @@
                             }
                         };
                 sSatelliteDatagramCallbackMap.put(callback, internalCallback);
-                return telephony.registerForIncomingDatagram(mSubId, internalCallback);
+                return telephony.registerForIncomingDatagram(internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -1678,7 +1694,7 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 if (internalCallback != null) {
-                    telephony.unregisterForIncomingDatagram(mSubId, internalCallback);
+                    telephony.unregisterForIncomingDatagram(internalCallback);
                 } else {
                     loge("unregisterForIncomingDatagram: No internal callback.");
                 }
@@ -1722,7 +1738,7 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.pollPendingDatagrams(mSubId, internalCallback);
+                telephony.pollPendingDatagrams(internalCallback);
             } else {
                 loge("pollPendingDatagrams() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
@@ -1780,7 +1796,7 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.sendDatagram(mSubId, datagramType, datagram,
+                telephony.sendDatagram(datagramType, datagram,
                         needFullScreenPointingUI, internalCallback);
             } else {
                 loge("sendDatagram() invalid telephony");
@@ -1898,7 +1914,7 @@
                         }
                     }
                 };
-                telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver);
+                telephony.requestTimeForNextSatelliteVisibility(receiver);
             } else {
                 loge("requestTimeForNextSatelliteVisibility() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -1929,7 +1945,7 @@
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                telephony.setDeviceAlignedWithSatellite(mSubId, isAligned);
+                telephony.setDeviceAlignedWithSatellite(isAligned);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -2193,7 +2209,7 @@
                         }
                     }
                 };
-                telephony.requestNtnSignalStrength(mSubId, receiver);
+                telephony.requestNtnSignalStrength(receiver);
             } else {
                 loge("requestNtnSignalStrength() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
@@ -2244,7 +2260,7 @@
                                                 ntnSignalStrength)));
                             }
                         };
-                telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
+                telephony.registerForNtnSignalStrengthChanged(internalCallback);
                 sNtnSignalStrengthCallbackMap.put(callback, internalCallback);
             } else {
                 throw new IllegalStateException("Telephony service is null.");
@@ -2284,7 +2300,7 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 if (internalCallback != null) {
-                    telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback);
+                    telephony.unregisterForNtnSignalStrengthChanged(internalCallback);
                 } else {
                     loge("unregisterForNtnSignalStrengthChanged: No internal callback.");
                     throw new IllegalArgumentException("callback is not valid");
@@ -2329,7 +2345,7 @@
                             }
                         };
                 sSatelliteCapabilitiesCallbackMap.put(callback, internalCallback);
-                return telephony.registerForCapabilitiesChanged(mSubId, internalCallback);
+                return telephony.registerForCapabilitiesChanged(internalCallback);
             } else {
                 throw new IllegalStateException("Telephony service is null.");
             }
@@ -2362,7 +2378,7 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 if (internalCallback != null) {
-                    telephony.unregisterForCapabilitiesChanged(mSubId, internalCallback);
+                    telephony.unregisterForCapabilitiesChanged(internalCallback);
                 } else {
                     loge("unregisterForCapabilitiesChanged: No internal callback.");
                 }
@@ -2438,7 +2454,7 @@
                         };
                 sSatelliteSupportedStateCallbackMap.put(callback, internalCallback);
                 return telephony.registerForSatelliteSupportedStateChanged(
-                        mSubId, internalCallback);
+                        internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -2473,7 +2489,7 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 if (internalCallback != null) {
-                    telephony.unregisterForSatelliteSupportedStateChanged(mSubId, internalCallback);
+                    telephony.unregisterForSatelliteSupportedStateChanged(internalCallback);
                 } else {
                     loge("unregisterForSupportedStateChanged: No internal callback.");
                 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index e66a082..8b51321 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -24,6 +24,7 @@
 import android.telephony.satellite.stub.ISatelliteListener;
 import android.telephony.satellite.stub.SatelliteDatagram;
 import android.telephony.satellite.stub.SystemSelectionSpecifier;
+import android.telephony.satellite.stub.SatelliteModemEnableRequestAttributes;
 
 /**
  * {@hide}
@@ -82,11 +83,15 @@
      * is enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
      * this may also re-enable the cellular modem.
      *
-     * @param enableSatellite True to enable the satellite modem and false to disable.
-     * @param enableDemoMode True to enable demo mode and false to disable.
-     * @param isEmergency To specify the satellite is enabled for emergency session and false for
-     * non emergency session. Note: it is possible that a emergency session started get converted
-     * to a non emergency session and vice versa.
+     * Framework might send an enable request to update the enable attributes of an already-started
+     * satellite session. In such cases, modem needs to apply the new enable attrbitues to the
+     * satellite session. Moreover, modem needs to report its current state and signal strength
+     * level to framework right after receiving this request from framework.
+     *
+     * Framework might send a disable request when an enable request is being processed. In such
+     * cases, modem needs to abort the enable request and process the disable request.
+     *
+     * @param enableAttributes The enable parameters that will be applied to the satellite session
      * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid result codes returned:
@@ -99,8 +104,8 @@
      *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode,
-            in boolean isEmergency, in IIntegerConsumer resultCallback);
+    void requestSatelliteEnabled(in SatelliteModemEnableRequestAttributes enableAttributes,
+            in IIntegerConsumer resultCallback);
 
     /**
      * Request to get whether the satellite modem is enabled.
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index c50e469..4f47210 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -89,12 +89,11 @@
         }
 
         @Override
-        public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
-                boolean isEmergency, IIntegerConsumer resultCallback) throws RemoteException {
+        public void requestSatelliteEnabled(SatelliteModemEnableRequestAttributes enableAttributes,
+                IIntegerConsumer resultCallback) throws RemoteException {
             executeMethodAsync(
                     () -> SatelliteImplBase.this
-                            .requestSatelliteEnabled(
-                                    enableSatellite, enableDemoMode, isEmergency, resultCallback),
+                            .requestSatelliteEnabled(enableAttributes, resultCallback),
                     "requestSatelliteEnabled");
         }
 
@@ -325,11 +324,7 @@
      * enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
      * this may also re-enable the cellular modem.
      *
-     * @param enableSatellite True to enable the satellite modem and false to disable.
-     * @param enableDemoMode True to enable demo mode and false to disable.
-     * @param isEmergency To specify the satellite is enabled for emergency session and false for
-     * non emergency session. Note: it is possible that a emergency session started get converted
-     * to a non emergency session and vice versa.
+     * @param enableAttributes The enable parameters that will be applied to the satellite session
      * @param resultCallback The callback to receive the error code result of the operation.
      *
      * Valid result codes returned:
@@ -342,8 +337,8 @@
      *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
      *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      */
-    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
-            boolean isEmergency, @NonNull IIntegerConsumer resultCallback) {
+    public void requestSatelliteEnabled(SatelliteModemEnableRequestAttributes enableAttributes,
+            @NonNull IIntegerConsumer resultCallback) {
         // stub implementation
     }
 
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
index 162fe2b..24377c7 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl
@@ -22,7 +22,7 @@
 @Backing(type="int")
 enum SatelliteModemState {
     /**
-     * Satellite modem is in idle state.
+     * Satellite Idle state during which modem will scan for TN networks.
      */
     SATELLITE_MODEM_STATE_IDLE = 0,
     /**
@@ -46,13 +46,13 @@
      */
     SATELLITE_MODEM_STATE_UNAVAILABLE = 5,
     /**
-     * The satellite modem is powered on but the device is not registered to a satellite cell.
+     * The satellite modem is powered on but the device is out of service.
      */
-    SATELLITE_MODEM_STATE_NOT_CONNECTED = 6,
+    SATELLITE_MODEM_STATE_OUT_OF_SERVICE = 6,
     /**
-     * The satellite modem is powered on and the device is registered to a satellite cell.
+     * The satellite modem is powered on and the device is registered and in service.
      */
-    SATELLITE_MODEM_STATE_CONNECTED = 7,
+    SATELLITE_MODEM_STATE_IN_SERVICE = 7,
     /**
      * Satellite modem state is unknown. This generic modem state should be used only when the
      * modem state cannot be mapped to other specific modem states.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0c5f30f..e852e6b 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2743,7 +2743,6 @@
     /**
      * Request to enable or disable the satellite modem.
      *
-     * @param subId The subId of the subscription to enable or disable the satellite modem for.
      * @param enableSatellite True to enable the satellite modem and false to disable.
      * @param enableDemoMode True if demo mode is enabled and false otherwise. When
      *                       disabling satellite, {@code enableDemoMode} is always considered as
@@ -2755,93 +2754,83 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode,
+    void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
             boolean isEmergency, in IIntegerConsumer callback);
 
     /**
      * Request to get whether the satellite modem is enabled.
      *
-     * @param subId The subId of the subscription to request whether satellite is enabled for.
      * @param receiver Result receiver to get the error code of the request and whether the
      *                 satellite modem is enabled.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestIsSatelliteEnabled(int subId, in ResultReceiver receiver);
+    void requestIsSatelliteEnabled(in ResultReceiver receiver);
 
     /**
      * Request to get whether the satellite service demo mode is enabled.
      *
-     * @param subId The subId of the subscription to request whether the satellite demo mode is
-     *              enabled for.
      * @param receiver Result receiver to get the error code of the request and whether the
      *                 satellite demo mode is enabled.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver);
+    void requestIsDemoModeEnabled(in ResultReceiver receiver);
 
     /**
      * Request to get whether the satellite service is enabled with emergency mode.
      *
-     * @param subId The subId of the subscription to request whether the satellite demo mode is
-     *              enabled for.
      * @param receiver Result receiver to get the error code of the request and whether the
      *                 satellite is enabled with emergency mode.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestIsEmergencyModeEnabled(int subId, in ResultReceiver receiver);
+    void requestIsEmergencyModeEnabled(in ResultReceiver receiver);
 
     /**
      * Request to get whether the satellite service is supported on the device.
      *
-     * @param subId The subId of the subscription to check whether satellite is supported for.
      * @param receiver Result receiver to get the error code of the request and whether the
      *                 satellite service is supported on the device.
      */
-    void requestIsSatelliteSupported(int subId, in ResultReceiver receiver);
+    void requestIsSatelliteSupported(in ResultReceiver receiver);
 
     /**
      * Request to get the capabilities of the satellite service.
      *
-     * @param subId The subId of the subscription to get the capabilities for.
      * @param receiver Result receiver to get the error code of the request and the requested
      *                 capabilities of the satellite service.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteCapabilities(int subId, in ResultReceiver receiver);
+    void requestSatelliteCapabilities(in ResultReceiver receiver);
 
     /**
      * Start receiving satellite transmission updates.
      *
-     * @param subId The subId of the subscription to stop satellite transmission updates for.
      * @param resultCallback The callback to get the result of the request.
      * @param callback The callback to handle transmission updates.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void startSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+    void startSatelliteTransmissionUpdates(in IIntegerConsumer resultCallback,
             in ISatelliteTransmissionUpdateCallback callback);
 
     /**
      * Stop receiving satellite transmission updates.
      *
-     * @param subId The subId of the subscritpion to stop satellite transmission updates for.
      * @param resultCallback The callback to get the result of the request.
      * @param callback The callback that was passed to startSatelliteTransmissionUpdates.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void stopSatelliteTransmissionUpdates(int subId, in IIntegerConsumer resultCallback,
+    void stopSatelliteTransmissionUpdates(in IIntegerConsumer resultCallback,
             in ISatelliteTransmissionUpdateCallback callback);
 
     /**
      * Register the subscription with a satellite provider.
      * This is needed to register the subscription if the provider allows dynamic registration.
      *
-     * @param subId The subId of the subscription to be provisioned.
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
      * @provisionData Data from the provisioning app that can be used by provisioning server
@@ -2851,7 +2840,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    ICancellationSignal provisionSatelliteService(int subId, in String token,
+    ICancellationSignal provisionSatelliteService(in String token,
             in byte[] provisionData, in IIntegerConsumer callback);
 
     /**
@@ -2861,110 +2850,99 @@
      * {@link SatelliteCallback.SatelliteProvisionStateListener#onSatelliteProvisionStateChanged}
      * should report as deprovisioned.
      *
-     * @param subId The subId of the subscription to be deprovisioned.
      * @param token The token of the device/subscription to be deprovisioned.
      * @param callback The callback to get the result of the request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback);
+    void deprovisionSatelliteService(in String token, in IIntegerConsumer callback);
 
     /**
      * Registers for provision state changed from satellite modem.
      *
-     * @param subId The subId of the subscription to register for provision state changed.
      * @param callback The callback to handle the satellite provision state changed event.
      *
      * @return The {@link SatelliteError} result of the operation.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteProvisionStateChanged(int subId,
-            in ISatelliteProvisionStateCallback callback);
+    int registerForSatelliteProvisionStateChanged(in ISatelliteProvisionStateCallback callback);
 
     /**
      * Unregisters for provision state changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for provision state changed.
      * @param callback The callback that was passed to registerForSatelliteProvisionStateChanged.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForSatelliteProvisionStateChanged(int subId,
+    void unregisterForSatelliteProvisionStateChanged(
             in ISatelliteProvisionStateCallback callback);
 
     /**
      * Request to get whether the device is provisioned with a satellite provider.
      *
-     * @param subId The subId of the subscription to get whether the device is provisioned for.
      * @param receiver Result receiver to get the error code of the request and whether the
      *                 device is provisioned with a satellite provider.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestIsSatelliteProvisioned(int subId, in ResultReceiver receiver);
+    void requestIsSatelliteProvisioned(in ResultReceiver receiver);
 
     /**
      * Registers for modem state changed from satellite modem.
      *
-     * @param subId The subId of the subscription to register for satellite modem state changed.
      * @param callback The callback to handle the satellite modem state changed event.
      *
      * @return The {@link SatelliteError} result of the operation.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteModemStateChanged(int subId, ISatelliteModemStateCallback callback);
+    int registerForSatelliteModemStateChanged(ISatelliteModemStateCallback callback);
 
     /**
      * Unregisters for modem state changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for satellite modem state changed.
      * @param callback The callback that was passed to registerForSatelliteStateChanged.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForModemStateChanged(int subId, ISatelliteModemStateCallback callback);
+    void unregisterForModemStateChanged(ISatelliteModemStateCallback callback);
 
    /**
      * Register to receive incoming datagrams over satellite.
      *
-     * @param subId The subId of the subscription to register for incoming satellite datagrams.
      * @param callback The callback to handle the incoming datagrams.
      *
      * @return The {@link SatelliteError} result of the operation.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForIncomingDatagram(int subId, ISatelliteDatagramCallback callback);
+    int registerForIncomingDatagram(ISatelliteDatagramCallback callback);
 
    /**
      * Unregister to stop receiving incoming datagrams over satellite.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for incoming satellite datagrams.
      * @param callback The callback that was passed to registerForIncomingDatagram.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForIncomingDatagram(int subId, ISatelliteDatagramCallback callback);
+    void unregisterForIncomingDatagram(ISatelliteDatagramCallback callback);
 
    /**
     * Poll pending satellite datagrams over satellite.
     *
-    * @param subId The subId of the subscription used for receiving datagrams.
     * @param callback The callback to get the result of the request.
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
                 + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void pollPendingDatagrams(int subId, IIntegerConsumer callback);
+    void pollPendingDatagrams(IIntegerConsumer callback);
 
    /**
     * Send datagram over satellite.
     *
-    * @param subId The subId of the subscription to send satellite datagrams for.
     * @param datagramType Type of datagram.
     * @param datagram Datagram to send over satellite.
     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
@@ -2973,7 +2951,7 @@
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void sendDatagram(int subId, int datagramType, in SatelliteDatagram datagram,
+    void sendDatagram(int datagramType, in SatelliteDatagram datagram,
             in boolean needFullScreenPointingUI, IIntegerConsumer callback);
 
     /**
@@ -2991,13 +2969,12 @@
     /**
      * Request to get the time after which the satellite will be visible.
      *
-     * @param subId The subId to get the time after which the satellite will be visible for.
      * @param receiver Result receiver to get the error code of the request and the requested
      *                 time after which the satellite will be visible.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestTimeForNextSatelliteVisibility(int subId, in ResultReceiver receiver);
+    void requestTimeForNextSatelliteVisibility(in ResultReceiver receiver);
 
     /**
      * Inform whether the device is aligned with the satellite within in margin for demo mode.
@@ -3007,7 +2984,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void setDeviceAlignedWithSatellite(int subId, boolean isAligned);
+    void setDeviceAlignedWithSatellite(boolean isAligned);
 
     /**
      * This API can be used by only CTS to update satellite vendor service package name.
@@ -3163,20 +3140,18 @@
     /**
      * Request to get the signal strength of the satellite connection.
      *
-     * @param subId The subId of the subscription to request for.
      * @param receiver Result receiver to get the error code of the request and the current signal
      * strength of the satellite connection.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestNtnSignalStrength(int subId, in ResultReceiver receiver);
+    void requestNtnSignalStrength(in ResultReceiver receiver);
 
     /**
      * Registers for NTN signal strength changed from satellite modem. If the registration operation
      * is not successful, a {@link SatelliteException} that contains {@link SatelliteResult} will be
      * thrown.
      *
-     * @param subId The subId of the subscription to request for.
      * @param callback The callback to handle the NTN signal strength changed event. If the
      * operation is successful, {@link NtnSignalStrengthCallback#onNtnSignalStrengthChanged(
      * NtnSignalStrength)} will return an instance of {@link NtnSignalStrength} with a value of
@@ -3185,30 +3160,27 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void registerForNtnSignalStrengthChanged(int subId,
-            in INtnSignalStrengthCallback callback);
+    void registerForNtnSignalStrengthChanged(in INtnSignalStrengthCallback callback);
 
     /**
      * Unregisters for NTN signal strength changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for provision state changed.
      * @param callback The callback that was passed to
      * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
+    void unregisterForNtnSignalStrengthChanged(in INtnSignalStrengthCallback callback);
 
     /**
      * Registers for satellite capabilities change event from the satellite service.
      *
-     * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle the satellite capabilities changed event.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForCapabilitiesChanged(int subId, in ISatelliteCapabilitiesCallback callback);
+    int registerForCapabilitiesChanged(in ISatelliteCapabilitiesCallback callback);
 
     /**
      * Unregisters for satellite capabilities change event from the satellite service.
@@ -3219,8 +3191,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForCapabilitiesChanged(int subId,
-            in ISatelliteCapabilitiesCallback callback);
+    void unregisterForCapabilitiesChanged(in ISatelliteCapabilitiesCallback callback);
 
     /**
      * This API can be used by only CTS to override the cached value for the device overlay config
@@ -3329,26 +3300,24 @@
     /**
      * Registers for supported state changed from satellite modem.
      *
-     * @param subId The subId of the subscription to register for supported state changed.
      * @param callback The callback to handle the satellite supported state changed event.
      *
      * @return The {@link SatelliteError} result of the operation.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForSatelliteSupportedStateChanged(int subId,
+    int registerForSatelliteSupportedStateChanged(
             in ISatelliteSupportedStateCallback callback);
 
     /**
      * Unregisters for supported state changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for supported state changed.
      * @param callback The callback that was passed to registerForSatelliteSupportedStateChanged.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void unregisterForSatelliteSupportedStateChanged(int subId,
+    void unregisterForSatelliteSupportedStateChanged(
             in ISatelliteSupportedStateCallback callback);
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index 29286e8..b92ba66 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -169,13 +169,13 @@
 
     /**
      * Set to false to disable SMS receiving, default is
-     * the value of config_sms_capable
+     * the value of TelephonyManager.isDeviceSmsCapable
      */
     static final String PROPERTY_SMS_RECEIVE = "telephony.sms.receive";
 
     /**
      * Set to false to disable SMS sending, default is
-     * the value of config_sms_capable
+     * the value of TelephonyManager.isDeviceSmsCapable
      */
     static final String PROPERTY_SMS_SEND = "telephony.sms.send";
 
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index 379b45c..c3e1a1f 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -24,6 +24,7 @@
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -177,6 +178,13 @@
     @Ignore("Not applicable to this CUJ.")
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
 
+    @FlakyTest(bugId = 342596801)
+    override fun entireScreenCovered() = super.entireScreenCovered()
+
+    @FlakyTest(bugId = 342596801)
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
     companion object {
         /** {@inheritDoc} */
         private var startDisplayBounds = Rect()
diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index adeba72..6e6a327 100644
--- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -196,7 +196,7 @@
         super.appLayerBecomesVisible()
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 338296297)
     @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
index 8040610..cfc818b 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
@@ -31,8 +31,7 @@
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
 class CloseAppBackButton3ButtonLandscape :
     CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
-    // TODO: Missing CUJ (b/300078127)
-    @ExpectedScenarios(["ENTIRE_TRACE"])
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
     @Test
     override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
 
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
index aacccf4..6bf32a8 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
@@ -31,8 +31,7 @@
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
 class CloseAppBackButton3ButtonPortrait :
     CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
-    // TODO: Missing CUJ (b/300078127)
-    @ExpectedScenarios(["ENTIRE_TRACE"])
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
     @Test
     override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
 
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
index 74ee460..4b6ab77 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
@@ -31,8 +31,7 @@
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
 class CloseAppBackButtonGesturalNavLandscape :
     CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
-    // TODO: Missing CUJ (b/300078127)
-    @ExpectedScenarios(["ENTIRE_TRACE"])
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
     @Test
     override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
 
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
index 57463c3..7cc9db0 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
@@ -31,8 +31,7 @@
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
 class CloseAppBackButtonGesturalNavPortrait :
     CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
-    // TODO: Missing CUJ (b/300078127)
-    @ExpectedScenarios(["ENTIRE_TRACE"])
+    @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
     @Test
     override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
 
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 8811e00..3f6a0bf 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -16,12 +16,17 @@
 
 package com.android.server.wm.flicker.helpers
 
+import android.content.Context
+import android.graphics.Insets
 import android.graphics.Rect
+import android.graphics.Region
 import android.platform.uiautomator_helpers.DeviceHelpers
 import android.tools.device.apphelpers.IStandardAppHelper
 import android.tools.helpers.SYSTEMUI_PACKAGE
 import android.tools.traces.parsers.WindowManagerStateHelper
 import android.tools.traces.wm.WindowingMode
+import android.view.WindowInsets
+import android.view.WindowManager
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
 import androidx.test.uiautomator.UiDevice
@@ -43,6 +48,13 @@
         RIGHT_BOTTOM
     }
 
+    enum class Edges {
+        LEFT,
+        RIGHT,
+        TOP,
+        BOTTOM
+    }
+
     /** Wait for an app moved to desktop to finish its transition. */
     private fun waitForAppToMoveToDesktop(wmHelper: WindowManagerStateHelper) {
         wmHelper
@@ -70,9 +82,7 @@
         // Start dragging a little under the top to prevent dragging the notification shade.
         val startY = 10
 
-        val displayRect =
-            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
-                ?: throw IllegalStateException("Default display is null")
+        val displayRect = getDisplayRect(wmHelper)
 
         // The position we want to drag to
         val endY = displayRect.centerY() / 2
@@ -81,18 +91,62 @@
         device.drag(startX, startY, startX, endY, 100)
     }
 
+    private fun getMaximizeButtonForTheApp(caption: UiObject2?): UiObject2 {
+        return caption
+            ?.children
+            ?.find { it.resourceName.endsWith(MAXIMIZE_BUTTON_VIEW) }
+            ?.children
+            ?.get(0)
+            ?: error("Unable to find resource $MAXIMIZE_BUTTON_VIEW\n")
+    }
+
     /** Click maximise button on the app header for the given app. */
     fun maximiseDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
         val caption = getCaptionForTheApp(wmHelper, device)
-        val maximizeButton =
-            caption
-                ?.children
-                ?.find { it.resourceName.endsWith(MAXIMIZE_BUTTON_VIEW) }
-                ?.children
-                ?.get(0)
-        maximizeButton?.click()
+        val maximizeButton = getMaximizeButtonForTheApp(caption)
+        maximizeButton.click()
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
+
+    /** Open maximize menu and click snap resize button on the app header for the given app. */
+    fun snapResizeDesktopApp(
+        wmHelper: WindowManagerStateHelper,
+        device: UiDevice,
+        context: Context,
+        toLeft: Boolean
+    ) {
+        val caption = getCaptionForTheApp(wmHelper, device)
+        val maximizeButton = getMaximizeButtonForTheApp(caption)
+        maximizeButton?.longClick()
+        wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
+
+        val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON
+        val maximizeMenu = getDesktopAppViewByRes(MAXIMIZE_MENU)
+
+        val snapResizeButton =
+            maximizeMenu
+                ?.wait(Until.findObject(By.res(SYSTEMUI_PACKAGE, buttonResId)), TIMEOUT.toMillis())
+                ?: error("Unable to find object with resource id $buttonResId")
+        snapResizeButton.click()
+
+        val displayRect = getDisplayRect(wmHelper)
+        val insets = getWindowInsets(
+            context, WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()
+        )
+        displayRect.inset(insets)
+
+        val expectedWidth = displayRect.width() / 2
+        val expectedRect = Rect(displayRect).apply {
+            if (toLeft) right -= expectedWidth else left += expectedWidth
+        }
+
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .withSurfaceVisibleRegion(this, Region(expectedRect))
+            .waitForAndVerify()
+    }
+
     /** Click close button on the app header for the given app. */
     fun closeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
         val caption = getCaptionForTheApp(wmHelper, device)
@@ -112,8 +166,7 @@
         if (
             wmHelper.getWindow(innerHelper)?.windowingMode !=
             WindowingMode.WINDOWING_MODE_FREEFORM.value
-        )
-            error("expected a freeform window with caption but window is not in freeform mode")
+        ) error("expected a freeform window with caption but window is not in freeform mode")
         val captions =
             device.wait(Until.findObjects(caption), TIMEOUT.toMillis())
                 ?: error("Unable to find view $caption\n")
@@ -142,6 +195,40 @@
         dragWindow(startX, startY, endX, endY, wmHelper, device)
     }
 
+    /** Resize a desktop app from its edges. */
+    fun edgeResize(
+        wmHelper: WindowManagerStateHelper,
+        motionEvent: MotionEventHelper,
+        edge: Edges
+    ) {
+        val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+        val (startX, startY) = getStartCoordinatesForEdgeResize(windowRect, edge)
+        val verticalChange = when (edge) {
+            Edges.LEFT -> 0
+            Edges.RIGHT -> 0
+            Edges.TOP -> -100
+            Edges.BOTTOM -> 100
+        }
+        val horizontalChange = when (edge) {
+            Edges.LEFT -> -100
+            Edges.RIGHT -> 100
+            Edges.TOP -> 0
+            Edges.BOTTOM -> 0
+        }
+
+        // The position we want to drag to
+        val endY = startY + verticalChange
+        val endX = startX + horizontalChange
+
+        motionEvent.actionDown(startX, startY)
+        motionEvent.actionMove(startX, startY, endX, endY, /* steps= */100)
+        motionEvent.actionUp(endX, endY)
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .waitForAndVerify()
+    }
+
     /** Drag a window from a source coordinate to a destination coordinate. */
     fun dragWindow(
         startX: Int, startY: Int,
@@ -156,6 +243,30 @@
             .waitForAndVerify()
     }
 
+    /** Drag a window to a snap resize region, found at the left and right edges of the screen. */
+    fun dragToSnapResizeRegion(
+        wmHelper: WindowManagerStateHelper,
+        device: UiDevice,
+        isLeft: Boolean,
+    ) {
+        val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
+        // Set start x-coordinate as center of app header.
+        val startX = windowRect.centerX()
+        val startY = windowRect.top
+
+        val displayRect = getDisplayRect(wmHelper)
+
+        val endX = if (isLeft) displayRect.left else displayRect.right
+        val endY = displayRect.centerY() / 2
+
+        // drag the window to snap resize
+        device.drag(startX, startY, endX, endY, /* steps= */ 100)
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .waitForAndVerify()
+    }
+
     private fun getStartCoordinatesForCornerResize(
         windowRect: Rect,
         corner: Corners
@@ -168,6 +279,18 @@
         }
     }
 
+    private fun getStartCoordinatesForEdgeResize(
+        windowRect: Rect,
+        edge: Edges
+    ): Pair<Int, Int> {
+        return when (edge) {
+            Edges.LEFT -> Pair(windowRect.left, windowRect.bottom / 2)
+            Edges.RIGHT -> Pair(windowRect.right, windowRect.bottom / 2)
+            Edges.TOP -> Pair(windowRect.right / 2, windowRect.top)
+            Edges.BOTTOM -> Pair(windowRect.right / 2, windowRect.bottom)
+        }
+    }
+
     /** Exit desktop mode by dragging the app handle to the top drag zone. */
     fun exitDesktopWithDragToTopDragZone(
         wmHelper: WindowManagerStateHelper,
@@ -179,9 +302,7 @@
 
     private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
         val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
-        val displayRect =
-            wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
-                ?: throw IllegalStateException("Default display is null")
+        val displayRect = getDisplayRect(wmHelper)
 
         val startX = windowRect.centerX()
         val endX = displayRect.centerX()
@@ -194,7 +315,8 @@
 
     fun enterDesktopModeFromAppHandleMenu(
         wmHelper: WindowManagerStateHelper,
-        device: UiDevice) {
+        device: UiDevice
+    ) {
         val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
         val startX = windowRect.centerX()
         // Click a little under the top to prevent opening the notification shade.
@@ -204,7 +326,7 @@
         device.click(startX, startY)
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
 
-        val pill = getAppHandlePillForWindow()
+        val pill = getDesktopAppViewByRes(PILL_CONTAINER)
         val desktopModeButton =
             pill
                 ?.children
@@ -214,10 +336,13 @@
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
 
-    private fun getAppHandlePillForWindow(): UiObject2? {
-        val pillContainer: BySelector = By.res(SYSTEMUI_PACKAGE, PILL_CONTAINER)
-        return DeviceHelpers.waitForObj(pillContainer, TIMEOUT)
-    }
+    private fun getDesktopAppViewByRes(viewResId: String): UiObject2? =
+        DeviceHelpers.waitForObj(By.res(SYSTEMUI_PACKAGE, viewResId), TIMEOUT)
+
+    private fun getDisplayRect(wmHelper: WindowManagerStateHelper): Rect =
+        wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
+            ?: throw IllegalStateException("Default display is null")
+
 
     /** Wait for transition to full screen to finish. */
     private fun waitForTransitionToFullscreen(wmHelper: WindowManagerStateHelper) {
@@ -228,13 +353,23 @@
             .waitForAndVerify()
     }
 
+    private fun getWindowInsets(context: Context, typeMask: Int): Insets {
+        val wm: WindowManager = context.getSystemService(WindowManager::class.java)
+            ?: error("Unable to connect to WindowManager service")
+        val metricInsets = wm.currentWindowMetrics.windowInsets
+        return metricInsets.getInsetsIgnoringVisibility(typeMask)
+    }
+
     private companion object {
-        val TIMEOUT = Duration.ofSeconds(3)
-        val CAPTION = "desktop_mode_caption"
-        val MAXIMIZE_BUTTON_VIEW = "maximize_button_view"
-        val CLOSE_BUTTON = "close_window"
-        val PILL_CONTAINER = "windowing_pill"
-        val DESKTOP_MODE_BUTTON = "desktop_button"
+        val TIMEOUT: Duration = Duration.ofSeconds(3)
+        const val CAPTION: String = "desktop_mode_caption"
+        const val MAXIMIZE_BUTTON_VIEW: String = "maximize_button_view"
+        const val MAXIMIZE_MENU: String = "maximize_menu"
+        const val CLOSE_BUTTON: String = "close_window"
+        const val PILL_CONTAINER: String = "windowing_pill"
+        const val DESKTOP_MODE_BUTTON: String = "desktop_button"
+        const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
+        const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
         val caption: BySelector
             get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
     }
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt
new file mode 100644
index 0000000..0835398
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.os.SystemClock
+import android.view.ContentInfo.Source
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_STYLUS
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import android.view.MotionEvent.TOOL_TYPE_FINGER
+import android.view.MotionEvent.TOOL_TYPE_MOUSE
+import android.view.MotionEvent.TOOL_TYPE_STYLUS
+import android.view.MotionEvent.ToolType
+
+/**
+ * Helper class for injecting a custom motion event and performing some actions. This is used for
+ * instrumenting input injections like stylus, mouse and touchpad.
+ */
+class MotionEventHelper(
+    private val instr: Instrumentation,
+    private val inputMethod: InputMethod
+) {
+    enum class InputMethod(@ToolType val toolType: Int, @Source val source: Int) {
+        STYLUS(TOOL_TYPE_STYLUS, SOURCE_STYLUS),
+        MOUSE(TOOL_TYPE_MOUSE, SOURCE_MOUSE),
+        TOUCHPAD(TOOL_TYPE_FINGER, SOURCE_MOUSE)
+    }
+
+    fun actionDown(x: Int, y: Int) {
+        injectMotionEvent(ACTION_DOWN, x, y)
+    }
+
+    fun actionUp(x: Int, y: Int) {
+        injectMotionEvent(ACTION_UP, x, y)
+    }
+
+    fun actionMove(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int) {
+        val incrementX = (endX - startX).toFloat() / (steps - 1)
+        val incrementY = (endY - startY).toFloat() / (steps - 1)
+
+        for (i in 0..steps) {
+            val time = SystemClock.uptimeMillis()
+            val x = startX + incrementX * i
+            val y = startY + incrementY * i
+
+            val moveEvent = getMotionEvent(time, time, ACTION_MOVE, x, y)
+            injectMotionEvent(moveEvent)
+        }
+    }
+
+    private fun injectMotionEvent(action: Int, x: Int, y: Int): MotionEvent {
+        val eventTime = SystemClock.uptimeMillis()
+        val event = getMotionEvent(eventTime, eventTime, action, x.toFloat(), y.toFloat())
+        injectMotionEvent(event)
+        return event
+    }
+
+    private fun injectMotionEvent(event: MotionEvent) {
+        instr.uiAutomation.injectInputEvent(event, true, false)
+    }
+
+    private fun getMotionEvent(
+        downTime: Long,
+        eventTime: Long,
+        action: Int,
+        x: Float,
+        y: Float,
+    ): MotionEvent {
+        val properties = MotionEvent.PointerProperties.createArray(1)
+        properties[0].toolType = inputMethod.toolType
+        properties[0].id = 1
+
+        val coords = MotionEvent.PointerCoords.createArray(1)
+        coords[0].x = x
+        coords[0].y = y
+        coords[0].pressure = 1f
+
+        val event =
+            MotionEvent.obtain(
+                downTime,
+                eventTime,
+                action,
+                /* pointerCount= */ 1,
+                properties,
+                coords,
+                /* metaState= */ 0,
+                /* buttonState= */ 0,
+                /* xPrecision = */ 1f,
+                /* yPrecision = */ 1f,
+                /* deviceId = */ 0,
+                /* edgeFlags = */ 0,
+                inputMethod.source,
+                /* flags = */ 0
+            )
+        event.displayId = 0
+        return event
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 43fd57b..931e4f8 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -269,9 +269,23 @@
     /** Expand the PIP window back to full screen via intent and wait until the app is visible */
     fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) = launchViaIntent(wmHelper)
 
-    fun changeAspectRatio() {
+    fun changeAspectRatio(wmHelper: WindowManagerStateHelper) {
         val intent = Intent("com.android.wm.shell.flicker.testapp.ASPECT_RATIO")
         context.sendBroadcast(intent)
+        // Wait on WMHelper on size change upon aspect ratio change
+        val windowRect = getWindowRect(wmHelper)
+        wmHelper
+            .StateSyncBuilder()
+            .add("pipAspectRatioChanged") {
+                val pipAppWindow =
+                    it.wmState.visibleWindows.firstOrNull { window ->
+                        this.windowMatchesAnyOf(window)
+                    }
+                        ?: return@add false
+                val pipRegion = pipAppWindow.frameRegion
+                return@add pipRegion != Region(windowRect)
+            }
+            .waitForAndVerify()
     }
 
     fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 06c2651..65398a2 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -40,7 +40,7 @@
         "frameworks-base-testutils",
         "hamcrest-library",
         "kotlin-test",
-        "mockito-target-minus-junit4",
+        "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "platform-screenshot-diff-core",
         "services.core.unboosted",
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/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
new file mode 100644
index 0000000..c7ebd3a
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewControllerTests.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input.debug;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.input.InputManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.InputDevice;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.input.InputManagerService;
+
+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;
+
+/**
+ * Build/Install/Run:
+ * atest TouchpadDebugViewControllerTests
+ */
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class TouchpadDebugViewControllerTests {
+    private static final int DEVICE_ID = 1000;
+    private static final String TAG = "TouchpadDebugViewController";
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    private Context mContext;
+    private TouchpadDebugViewController mTouchpadDebugViewController;
+    @Mock
+    private InputManager mInputManagerMock;
+    @Mock
+    private InputManagerService mInputManagerServiceMock;
+    @Mock
+    private WindowManager mWindowManagerMock;
+    private TestableLooper mTestableLooper;
+
+    @Before
+    public void setup() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        TestableContext mTestableContext = new TestableContext(mContext);
+        mTestableContext.addMockSystemService(WindowManager.class, mWindowManagerMock);
+
+        Rect bounds = new Rect(0, 0, 2560, 1600);
+        WindowMetrics metrics = new WindowMetrics(bounds, new WindowInsets(bounds), 1.0f);
+
+        when(mWindowManagerMock.getCurrentWindowMetrics()).thenReturn(metrics);
+
+        unMockTouchpad();
+
+        mTestableLooper = TestableLooper.get(this);
+
+        mTestableContext.addMockSystemService(InputManager.class, mInputManagerMock);
+
+        mTouchpadDebugViewController = new TouchpadDebugViewController(mTestableContext,
+                mTestableLooper.getLooper(), mInputManagerServiceMock);
+    }
+
+    private InputDevice createTouchpadInputDevice(int id) {
+        return new InputDevice.Builder()
+                .setId(id)
+                .setSources(InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)
+                .setName("Test Device " + id)
+                .build();
+    }
+
+    private void mockTouchpad() {
+        when(mInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID});
+        when(mInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(
+                createTouchpadInputDevice(DEVICE_ID));
+    }
+
+    private void unMockTouchpad() {
+        when(mInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{});
+        when(mInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn(null);
+    }
+
+    @Test
+    public void touchpadConnectedWhileSettingDisabled() throws Exception {
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+        mockTouchpad();
+        mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+        verify(mWindowManagerMock, never()).addView(any(), any());
+        verify(mWindowManagerMock, never()).removeView(any());
+    }
+
+    @Test
+    public void settingEnabledWhileNoTouchpadConnected() throws Exception {
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+        verify(mWindowManagerMock, never()).addView(any(), any());
+        verify(mWindowManagerMock, never()).removeView(any());
+    }
+
+    @Test
+    public void touchpadConnectedWhileSettingEnabled() throws Exception {
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+        mockTouchpad();
+        mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+        verify(mWindowManagerMock, times(1)).addView(any(), any());
+        verify(mWindowManagerMock, never()).removeView(any());
+    }
+
+    @Test
+    public void touchpadConnectedWhileSettingEnabledThenDisabled() throws Exception {
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+        mockTouchpad();
+        mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+        verify(mWindowManagerMock, times(1)).addView(any(), any());
+        verify(mWindowManagerMock, never()).removeView(any());
+
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+        verify(mWindowManagerMock, times(1)).addView(any(), any());
+        verify(mWindowManagerMock, times(1)).removeView(any());
+    }
+
+    @Test
+    public void touchpadConnectedWhileSettingDisabledThenEnabled() throws Exception {
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+        mockTouchpad();
+        mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+        verify(mWindowManagerMock, never()).addView(any(), any());
+        verify(mWindowManagerMock, never()).removeView(any());
+
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+        verify(mWindowManagerMock, times(1)).addView(any(), any());
+        verify(mWindowManagerMock, never()).removeView(any());
+    }
+
+    @Test
+    public void touchpadConnectedWhileSettingDisabledThenTouchpadDisconnected() throws Exception {
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+        mockTouchpad();
+        mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+        verify(mWindowManagerMock, never()).addView(any(), any());
+        verify(mWindowManagerMock, never()).removeView(any());
+
+        unMockTouchpad();
+        mTouchpadDebugViewController.onInputDeviceRemoved(DEVICE_ID);
+
+        verify(mWindowManagerMock, never()).addView(any(), any());
+        verify(mWindowManagerMock, never()).removeView(any());
+    }
+
+    @Test
+    public void touchpadConnectedWhileSettingEnabledThenTouchpadDisconnectedThenSettingDisabled()
+            throws Exception {
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(true);
+
+        mockTouchpad();
+        mTouchpadDebugViewController.onInputDeviceAdded(DEVICE_ID);
+
+        verify(mWindowManagerMock, times(1)).addView(any(), any());
+        verify(mWindowManagerMock, never()).removeView(any());
+
+        unMockTouchpad();
+        mTouchpadDebugViewController.onInputDeviceRemoved(DEVICE_ID);
+
+        verify(mWindowManagerMock, times(1)).addView(any(), any());
+        verify(mWindowManagerMock, times(1)).removeView(any());
+
+        mTouchpadDebugViewController.updateTouchpadVisualizerEnabled(false);
+
+        verify(mWindowManagerMock, times(1)).addView(any(), any());
+        verify(mWindowManagerMock, times(1)).removeView(any());
+    }
+}
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
new file mode 100644
index 0000000..ad0ef1b
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input.debug;
+
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.testing.TestableContext;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.input.MotionEventBuilder;
+import com.android.cts.input.PointerBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Build/Install/Run:
+ * atest TouchpadDebugViewTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class TouchpadDebugViewTest {
+    private static final int TOUCHPAD_DEVICE_ID = 6;
+
+    private TouchpadDebugView mTouchpadDebugView;
+    private WindowManager.LayoutParams mWindowLayoutParams;
+
+    @Mock
+    WindowManager mWindowManager;
+
+    Rect mWindowBounds;
+    WindowMetrics mWindowMetrics;
+    TestableContext mTestableContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        mTestableContext = new TestableContext(context);
+
+        mTestableContext.addMockSystemService(WindowManager.class, mWindowManager);
+
+        mWindowBounds = new Rect(0, 0, 2560, 1600);
+        mWindowMetrics = new WindowMetrics(mWindowBounds, new WindowInsets(mWindowBounds), 1.0f);
+
+        when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+
+        mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID);
+
+        mTouchpadDebugView.measure(
+                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+        );
+
+        doAnswer(invocation -> {
+            mTouchpadDebugView.layout(0, 0, mTouchpadDebugView.getMeasuredWidth(),
+                    mTouchpadDebugView.getMeasuredHeight());
+            return null;
+        }).when(mWindowManager).addView(any(), any());
+
+        doAnswer(invocation -> {
+            mTouchpadDebugView.layout(0, 0, mTouchpadDebugView.getMeasuredWidth(),
+                    mTouchpadDebugView.getMeasuredHeight());
+            return null;
+        }).when(mWindowManager).updateViewLayout(any(), any());
+
+        mWindowLayoutParams = mTouchpadDebugView.getWindowLayoutParams();
+        mWindowLayoutParams.x = 20;
+        mWindowLayoutParams.y = 20;
+
+        mTouchpadDebugView.layout(0, 0, mTouchpadDebugView.getMeasuredWidth(),
+                mTouchpadDebugView.getMeasuredHeight());
+    }
+
+    @Test
+    public void testDragView() {
+        // Initial view position relative to screen.
+        int initialX = mWindowLayoutParams.x;
+        int initialY = mWindowLayoutParams.y;
+
+        float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+        float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10;
+
+        // Simulate ACTION_DOWN event (initial touch).
+        MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(40f)
+                        .y(40f)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+        verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+
+        // Simulate ACTION_MOVE event (dragging to the right).
+        MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(40f + offsetX)
+                        .y(40f + offsetY)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+        ArgumentCaptor<WindowManager.LayoutParams> mWindowLayoutParamsCaptor =
+                ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+        verify(mWindowManager).updateViewLayout(any(), mWindowLayoutParamsCaptor.capture());
+
+        // Verify position after ACTION_MOVE
+        assertEquals(initialX + (long) offsetX, mWindowLayoutParamsCaptor.getValue().x);
+        assertEquals(initialY + (long) offsetY, mWindowLayoutParamsCaptor.getValue().y);
+
+        // Simulate ACTION_UP event (release touch).
+        MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(40f + offsetX)
+                        .y(40f + offsetY)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+        assertEquals(initialX + (long) offsetX, mWindowLayoutParamsCaptor.getValue().x);
+        assertEquals(initialY + (long) offsetY, mWindowLayoutParamsCaptor.getValue().y);
+    }
+
+    @Test
+    public void testDragViewOutOfBounds() {
+        int initialX = mWindowLayoutParams.x;
+        int initialY = mWindowLayoutParams.y;
+
+        MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(initialX + 10f)
+                        .y(initialY + 10f)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+        verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+
+        // Simulate ACTION_MOVE event (dragging far to the right and bottom, beyond screen bounds)
+        MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(mWindowBounds.width() + mTouchpadDebugView.getWidth())
+                        .y(mWindowBounds.height() + mTouchpadDebugView.getHeight())
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+        ArgumentCaptor<WindowManager.LayoutParams> mWindowLayoutParamsCaptor =
+                ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+        verify(mWindowManager).updateViewLayout(any(), mWindowLayoutParamsCaptor.capture());
+
+        // Verify the view has been clamped to the right and bottom edges of the screen
+        assertEquals(mWindowBounds.width() - mTouchpadDebugView.getWidth(),
+                mWindowLayoutParamsCaptor.getValue().x);
+        assertEquals(mWindowBounds.height() - mTouchpadDebugView.getHeight(),
+                mWindowLayoutParamsCaptor.getValue().y);
+
+        MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(mWindowBounds.width() + mTouchpadDebugView.getWidth())
+                        .y(mWindowBounds.height() + mTouchpadDebugView.getHeight())
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+        // Verify the view has been clamped to the right and bottom edges of the screen
+        assertEquals(mWindowBounds.width() - mTouchpadDebugView.getWidth(),
+                mWindowLayoutParamsCaptor.getValue().x);
+        assertEquals(mWindowBounds.height() - mTouchpadDebugView.getHeight(),
+                mWindowLayoutParamsCaptor.getValue().y);
+    }
+
+    @Test
+    public void testSlopOffset() {
+        int initialX = mWindowLayoutParams.x;
+        int initialY = mWindowLayoutParams.y;
+
+        float offsetX = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() / 2.0f;
+        float offsetY = -(ViewConfiguration.get(mTestableContext).getScaledTouchSlop() / 2.0f);
+
+        MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(initialX)
+                        .y(initialY)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+        MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(initialX + offsetX)
+                        .y(initialY + offsetY)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+        MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(initialX)
+                        .y(initialY)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionUp);
+
+        // In this case the updateViewLayout() method wouldn't be called because the drag
+        // distance hasn't exceeded the slop
+        verify(mWindowManager, times(0)).updateViewLayout(any(), any());
+    }
+
+    @Test
+    public void testViewReturnsToInitialPositionOnCancel() {
+        int initialX = mWindowLayoutParams.x;
+        int initialY = mWindowLayoutParams.y;
+
+        float offsetX = 50;
+        float offsetY = 50;
+
+        MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(initialX)
+                        .y(initialY)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionDown);
+
+        MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(initialX + offsetX)
+                        .y(initialY + offsetY)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionMove);
+
+        ArgumentCaptor<WindowManager.LayoutParams> mWindowLayoutParamsCaptor =
+                ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+        verify(mWindowManager).updateViewLayout(any(), mWindowLayoutParamsCaptor.capture());
+
+        assertEquals(initialX + (long) offsetX, mWindowLayoutParamsCaptor.getValue().x);
+        assertEquals(initialY + (long) offsetY, mWindowLayoutParamsCaptor.getValue().y);
+
+        // Simulate ACTION_CANCEL event (canceling the touch event stream)
+        MotionEvent actionCancel = new MotionEventBuilder(MotionEvent.ACTION_CANCEL,
+                SOURCE_TOUCHSCREEN)
+                .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER)
+                        .x(initialX + offsetX)
+                        .y(initialY + offsetY)
+                )
+                .build();
+        mTouchpadDebugView.dispatchTouchEvent(actionCancel);
+
+        // Verify the view returns to its initial position
+        verify(mWindowManager, times(2)).updateViewLayout(any(),
+                mWindowLayoutParamsCaptor.capture());
+        assertEquals(initialX, mWindowLayoutParamsCaptor.getValue().x);
+        assertEquals(initialY, mWindowLayoutParamsCaptor.getValue().y);
+    }
+}
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/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
new file mode 100644
index 0000000..9d56a92
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.internal.protolog;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.protolog.common.IProtoLogGroup;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test class for {@link ProtoLog}. */
+@SuppressWarnings("ConstantConditions")
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogTest {
+
+    @Test
+    public void canRunProtoLogInitMultipleTimes() {
+        ProtoLog.init(TEST_GROUP_1);
+        ProtoLog.init(TEST_GROUP_1);
+        ProtoLog.init(TEST_GROUP_2);
+        ProtoLog.init(TEST_GROUP_1, TEST_GROUP_2);
+
+        final var instance = ProtoLog.getSingleInstance();
+        Truth.assertThat(instance.getRegisteredGroups())
+                .containsExactly(TEST_GROUP_1, TEST_GROUP_2);
+    }
+
+    private static final IProtoLogGroup TEST_GROUP_1 = new ProtoLogGroup("TEST_TAG_1", 1);
+    private static final IProtoLogGroup TEST_GROUP_2 = new ProtoLogGroup("TEST_TAG_2", 2);
+
+    private static class ProtoLogGroup implements IProtoLogGroup {
+        private final boolean mEnabled;
+        private volatile boolean mLogToProto;
+        private volatile boolean mLogToLogcat;
+        private final String mTag;
+        private final int mId;
+
+        ProtoLogGroup(String tag, int id) {
+            this(true, true, false, tag, id);
+        }
+
+        ProtoLogGroup(
+                boolean enabled, boolean logToProto, boolean logToLogcat, String tag, int id) {
+            this.mEnabled = enabled;
+            this.mLogToProto = logToProto;
+            this.mLogToLogcat = logToLogcat;
+            this.mTag = tag;
+            this.mId = id;
+        }
+
+        @Override
+        public String name() {
+            return mTag;
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return mEnabled;
+        }
+
+        @Override
+        public boolean isLogToProto() {
+            return mLogToProto;
+        }
+
+        @Override
+        public boolean isLogToLogcat() {
+            return mLogToLogcat;
+        }
+
+        @Override
+        public boolean isLogToAny() {
+            return mLogToLogcat || mLogToProto;
+        }
+
+        @Override
+        public String getTag() {
+            return mTag;
+        }
+
+        @Override
+        public void setLogToProto(boolean logToProto) {
+            this.mLogToProto = logToProto;
+        }
+
+        @Override
+        public void setLogToLogcat(boolean logToLogcat) {
+            this.mLogToLogcat = logToLogcat;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
+}
diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp
index 4e75a1d..8888b32 100644
--- a/tests/TrustTests/Android.bp
+++ b/tests/TrustTests/Android.bp
@@ -40,3 +40,10 @@
     platform_apis: true,
     certificate: "platform",
 }
+
+test_module_config {
+    name: "TrustTests_trust_test",
+    base: "TrustTests",
+    test_suites: ["device-tests"],
+    include_filters: ["android.trust.test"],
+}
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/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index be63f82..498e431 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -306,6 +306,7 @@
   OutputFormat output_format = OutputFormat::kApk;
   std::unordered_set<std::string> extensions_to_not_compress;
   std::optional<std::regex> regex_to_not_compress;
+  FeatureFlagValues feature_flag_values;
 };
 
 // A sampling of public framework resource IDs.
@@ -672,6 +673,13 @@
               }
             }
 
+            FeatureFlagsFilterOptions flags_filter_options;
+            flags_filter_options.flags_must_be_readonly = true;
+            FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
+            if (!flags_filter.Consume(context_, doc.get())) {
+              return 1;
+            }
+
             error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values,
                                  false /*utf16*/, options_.output_format, archive_writer);
           }
@@ -1926,6 +1934,7 @@
         static_cast<bool>(options_.generate_proguard_rules_path);
     file_flattener_options.output_format = options_.output_format;
     file_flattener_options.do_not_fail_on_missing_resources = options_.merge_only;
+    file_flattener_options.feature_flag_values = options_.feature_flag_values;
 
     ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set);
     if (!file_flattener.Flatten(table, writer)) {
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
index 4866d2c..c456e5c 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -30,12 +30,14 @@
         "res/values/bools2.xml",
         "res/values/ints.xml",
         "res/values/strings.xml",
+        "res/layout/layout1.xml",
     ],
     out: [
         "values_bools.arsc.flat",
         "values_bools2.arsc.flat",
         "values_ints.arsc.flat",
         "values_strings.arsc.flat",
+        "layout_layout1.xml.flat",
     ],
     cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
         "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
@@ -52,7 +54,10 @@
     out: [
         "resapp.apk",
     ],
-    cmd: "$(location aapt2) link -o $(out) --manifest $(in)",
+    cmd: "$(location aapt2) link -o $(out) --manifest $(in) " +
+        "-I $(location :current_android_jar) " +
+        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
+    tool_files: [":current_android_jar"],
 }
 
 genrule {
@@ -66,7 +71,10 @@
     out: [
         "resource-flagging-java/com/android/intenal/flaggedresources/R.java",
     ],
-    cmd: "$(location aapt2) link -o $(genDir)/resapp.apk --java $(genDir)/resource-flagging-java --manifest $(in)",
+    cmd: "$(location aapt2) link -o $(genDir)/resapp.apk --java $(genDir)/resource-flagging-java --manifest $(in) " +
+        "-I $(location :current_android_jar) " +
+        "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
+    tool_files: [":current_android_jar"],
 }
 
 java_genrule {
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout1.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout1.xml
new file mode 100644
index 0000000..8b9ce13
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout1.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView android:id="@+id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <TextView android:id="@+id/disabled_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:featureFlag="test.package.falseFlag" />
+    <TextView android:id="@+id/text2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:featureFlag="test.package.trueFlag" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
index 3e094fb..1ed0c8a 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <bool name="res1">true</bool>
-    <bool name="res1" android:featureFlag="test.package.falseFlag">false</bool>
+    <bool name="bool1">true</bool>
+    <bool name="bool1" android:featureFlag="test.package.falseFlag">false</bool>
 
-    <bool name="res2">false</bool>
-    <bool name="res2" android:featureFlag="test.package.trueFlag">true</bool>
+    <bool name="bool2">false</bool>
+    <bool name="bool2" android:featureFlag="test.package.trueFlag">true</bool>
 
-    <bool name="res3">false</bool>
+    <bool name="bool3">false</bool>
 
-    <bool name="res4" android:featureFlag="test.package.falseFlag">true</bool>
+    <bool name="bool4" android:featureFlag="test.package.falseFlag">true</bool>
 </resources>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
index e7563aa..248c45f 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools2.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <bool name="res3" android:featureFlag="test.package.trueFlag">true</bool>
+    <bool name="bool3" android:featureFlag="test.package.trueFlag">true</bool>
 </resources>
\ No newline at end of file
diff --git a/tools/aapt2/link/FeatureFlagsFilter.cpp b/tools/aapt2/link/FeatureFlagsFilter.cpp
index 9d40db5..4e7c1b4 100644
--- a/tools/aapt2/link/FeatureFlagsFilter.cpp
+++ b/tools/aapt2/link/FeatureFlagsFilter.cpp
@@ -65,6 +65,13 @@
 
       if (auto it = feature_flag_values_.find(flag_name); it != feature_flag_values_.end()) {
         if (it->second.enabled.has_value()) {
+          if (options_.flags_must_be_readonly && !it->second.read_only) {
+            diagnostics_->Error(android::DiagMessage(node->line_number)
+                                << "attribute 'android:featureFlag' has flag '" << flag_name
+                                << "' which must be readonly but is not");
+            has_error_ = true;
+            return false;
+          }
           if (options_.remove_disabled_elements) {
             // Remove if flag==true && attr=="!flag" (negated) OR flag==false && attr=="flag"
             return *it->second.enabled == negated;
diff --git a/tools/aapt2/link/FeatureFlagsFilter.h b/tools/aapt2/link/FeatureFlagsFilter.h
index 1d342a7..61e4c80 100644
--- a/tools/aapt2/link/FeatureFlagsFilter.h
+++ b/tools/aapt2/link/FeatureFlagsFilter.h
@@ -38,6 +38,10 @@
   // If true, `Consume()` will return false (error) if a flag was found whose value in
   // `feature_flag_values` is not defined (std::nullopt).
   bool flags_must_have_value = true;
+
+  // If true, `Consume()` will return false (error) if a flag was found whose value in
+  // `feature_flag_values` is not readonly.
+  bool flags_must_be_readonly = false;
 };
 
 // Looks for the `android:featureFlag` attribute in each XML element, validates the flag names and
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index c901b58..3db37c2 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -84,7 +84,7 @@
   std::string output;
   DumpChunksToString(loaded_apk.get(), &output);
 
-  ASSERT_EQ(output.find("res4"), std::string::npos);
+  ASSERT_EQ(output.find("bool4"), std::string::npos);
   ASSERT_EQ(output.find("str1"), std::string::npos);
 }
 
@@ -94,7 +94,7 @@
   std::string r_contents;
   ::android::base::ReadFileToString(r_path, &r_contents);
 
-  ASSERT_NE(r_contents.find("public static final int res4"), std::string::npos);
+  ASSERT_NE(r_contents.find("public static final int bool4"), std::string::npos);
   ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos);
 }
 
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 682adbc..ea77b8d 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -118,7 +118,6 @@
 
 java_test_host {
     name: "hoststubgentest",
-    // main_class: "com.android.hoststubgen.Main",
     srcs: ["test/**/*.kt"],
     static_libs: [
         "hoststubgen",
@@ -143,8 +142,7 @@
     // "--policy-override-file $(location framework-policy-override.txt) " +
     "@$(location :hoststubgen-standard-options) " +
 
-    "--out-stub-jar $(location host_stub.jar) " +
-    "--out-impl-jar $(location host_impl.jar) " +
+    "--out-jar $(location host.jar) " +
 
     // "--keep-all-classes " + // Used it for an experiment. See KeepAllClassesFilter.
     "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
@@ -159,10 +157,8 @@
     srcs: [
         ":hoststubgen-standard-options",
     ],
-    // Create two jar files.
     out: [
-        "host_stub.jar",
-        "host_impl.jar",
+        "host.jar",
 
         // Following files are created just as FYI.
         "hoststubgen_keep_all.txt",
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStub.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStub.java
deleted file mode 100644
index cabdfe0..0000000
--- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStub.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.hosttest.annotation;
-
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
- * QUESTIONS ABOUT IT.
- *
- * Mark a class, field or a method as "Stub", meaning tests can see the APIs.
- * When applied to a class, it will _not_ affect the visibility of its members. They need to be
- * individually marked.
- *
- * <p>In order to expose a class and all its members, use {@link HostSideTestWholeClassStub}
- * instead.
- *
- * @hide
- */
-@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
-@Retention(RetentionPolicy.CLASS)
-public @interface HostSideTestStub {
-}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassStub.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassStub.java
deleted file mode 100644
index 1824f6f..0000000
--- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassStub.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.hosttest.annotation;
-
-import static java.lang.annotation.ElementType.TYPE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
- * QUESTIONS ABOUT IT.
- *
- * Same as {@link HostSideTestStub} but it'll change the visibility of all its members too.
- *
- * @hide
- */
-@Target({TYPE})
-@Retention(RetentionPolicy.CLASS)
-public @interface HostSideTestWholeClassStub {
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenKeptInStub.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenKeptInStub.java
deleted file mode 100644
index 12b9875..0000000
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenKeptInStub.java
+++ /dev/null
@@ -1,38 +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.hoststubgen.hosthelper;
-
-import static java.lang.annotation.ElementType.CONSTRUCTOR;
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation injected to all classes/methods/fields that are kept in the "stub" jar.
- *
- * All items in the stub jar are automatically kept in the impl jar as well, so
- * the items with this annotation will all have {@link HostStubGenKeptInImpl} too.
- */
-@Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface HostStubGenKeptInStub {
-    String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenKeptInStub.class);
-    String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
-}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsIgnore.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsIgnore.java
index cb50404..b017103 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsIgnore.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsIgnore.java
@@ -23,8 +23,6 @@
 
 /**
  * Annotation injected to all methods processed as "ignore".
- *
- * (This annotation is only added in the impl jar, but not the stub jar)
  */
 @Target({METHOD})
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenKeptInImpl.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsKeep.java
similarity index 92%
rename from tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenKeptInImpl.java
rename to tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsKeep.java
index 2cc500f..18ef1ba 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenKeptInImpl.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsKeep.java
@@ -25,11 +25,11 @@
 import java.lang.annotation.Target;
 
 /**
- * Annotation injected to all classes/methods/fields that are kept in the "impl" jar.
+ * Annotation injected to all classes/methods/fields that are kept in the processes jar.
  */
 @Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
 @Retention(RetentionPolicy.RUNTIME)
-public @interface HostStubGenKeptInImpl {
-    String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenKeptInImpl.class);
+public @interface HostStubGenProcessedAsKeep {
+    String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedAsKeep.class);
     String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
 }
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsSubstitute.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsSubstitute.java
index cfa4896..99e38c0 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsSubstitute.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsSubstitute.java
@@ -26,8 +26,6 @@
 
 /**
  * Annotation injected to all methods that are processed as "substitute".
- *
- * (This annotation is only added in the impl jar, but not the stub jar)
  */
 @Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsThrow.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsThrow.java
index 0d2da11..4933cf8 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsThrow.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsThrow.java
@@ -23,8 +23,6 @@
 
 /**
  * Annotation injected to all methods that are processed as "throw".
- *
- * (This annotation is only added in the impl jar, but not the stub jar)
  */
 @Target({METHOD})
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 60eb47ee..78fd8f7 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -16,12 +16,8 @@
 package com.android.hoststubgen.hosthelper;
 
 import java.io.PrintStream;
-import java.lang.StackWalker.Option;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.util.HashMap;
-
-import javax.annotation.concurrent.GuardedBy;
 
 /**
  * Utilities used in the host side test environment.
@@ -101,68 +97,6 @@
                 + methodName + methodDescriptor);
     }
 
-    private static final StackWalker sStackWalker =
-            StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
-
-    /**
-     * Return a {@link StackWalker} that supports {@link StackWalker#getCallerClass()}.
-     */
-    public static StackWalker getStackWalker() {
-        return sStackWalker;
-    }
-
-    /**
-     * Cache used by {@link #isClassAllowedToCallNonStubMethods}.
-     */
-    @GuardedBy("sAllowedClasses")
-    private static final HashMap<Class, Boolean> sAllowedClasses = new HashMap();
-
-    /**
-     * Return true if a given class is allowed to access non-stub methods -- that is, if the class
-     * is in the hoststubgen generated JARs. (not in the test jar.)
-     */
-    private static boolean isClassAllowedToCallNonStubMethods(Class<?> clazz) {
-        synchronized (sAllowedClasses) {
-            var cached = sAllowedClasses.get(clazz);
-            if (cached != null) {
-                return cached;
-            }
-        }
-        // All processed classes have this annotation.
-        var allowed = clazz.getAnnotation(HostStubGenKeptInImpl.class) != null;
-
-        // Java classes should be able to access any methods. (via callbacks, etc.)
-        if (!allowed) {
-            if (clazz.getPackageName().startsWith("java.")
-                    || clazz.getPackageName().startsWith("javax.")) {
-                allowed = true;
-            }
-        }
-        synchronized (sAllowedClasses) {
-            sAllowedClasses.put(clazz, allowed);
-        }
-        return allowed;
-    }
-
-    /**
-     * Called when non-stub methods are called. We do a host-unsupported method direct call check
-     * in here.
-     */
-    public static void onNonStubMethodCalled(
-            String methodClass,
-            String methodName,
-            String methodDescriptor,
-            Class<?> callerClass) {
-        if (SKIP_NON_STUB_METHOD_CHECK) {
-            return;
-        }
-        if (isClassAllowedToCallNonStubMethods(callerClass)) {
-            return; // Generated class is allowed to call framework class.
-        }
-        logPrintStream.println("! " + methodClass + "." + methodName + methodDescriptor
-                + " called by " + callerClass.getCanonicalName());
-    }
-
     /**
      * Called when any top level class (not nested classes) in the impl jar is loaded.
      *
diff --git a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt
index c371b5d..e72c9a4 100644
--- a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt
+++ b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt
@@ -3,8 +3,6 @@
 --debug
 
 # Uncomment below lines to enable each feature.
---enable-non-stub-method-check
-# --no-non-stub-method-check
 
 #--default-method-call-hook
 #    com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -13,15 +11,10 @@
 
 # Standard annotations.
 # Note, each line is a single argument, so we need newlines after each `--xxx-annotation`.
---stub-annotation
-    android.hosttest.annotation.HostSideTestStub
 
 --keep-annotation
     android.hosttest.annotation.HostSideTestKeep
 
---stub-class-annotation
-    android.hosttest.annotation.HostSideTestWholeClassStub
-
 --keep-class-annotation
     android.hosttest.annotation.HostSideTestWholeClassKeep
 
diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
index d97dd7c..5f0368a 100755
--- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
+++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
@@ -43,9 +43,8 @@
 
 cleanup_temp
 
-JAR=hoststubgen-test-tiny-framework.jar
-STUB=$TEMP/stub.jar
-IMPL=$TEMP/impl.jar
+INJAR=hoststubgen-test-tiny-framework.jar
+OUTJAR=$TEMP/host.jar
 
 ANNOTATION_FILTER=$TEMP/annotation-filter.txt
 
@@ -81,27 +80,18 @@
     cat $ANNOTATION_FILTER
   fi
 
-  local stub_arg=""
-  local impl_arg=""
+  local out_arg=""
 
-  if [[ "$STUB" != "" ]] ; then
-    stub_arg="--out-stub-jar $STUB"
-  fi
-  if [[ "$IMPL" != "" ]] ; then
-    impl_arg="--out-impl-jar $IMPL"
+  if [[ "$OUTJAR" != "" ]] ; then
+    out_arg="--out-jar $OUTJAR"
   fi
 
   hoststubgen \
       --debug \
-      --in-jar $JAR \
-      $stub_arg \
-      $impl_arg \
-      --stub-annotation \
-          android.hosttest.annotation.HostSideTestStub \
+      --in-jar $INJAR \
+      $out_arg \
       --keep-annotation \
           android.hosttest.annotation.HostSideTestKeep \
-      --stub-class-annotation \
-          android.hosttest.annotation.HostSideTestWholeClassStub \
       --keep-class-annotation \
           android.hosttest.annotation.HostSideTestWholeClassKeep \
       --throw-annotation \
@@ -213,9 +203,9 @@
 "
 
 run_hoststubgen_for_failure "One specific class disallowed" \
-    "TinyFrameworkClassAnnotations is not allowed to have Ravenwood annotations" \
+    "TinyFrameworkAnnotations is not allowed to have Ravenwood annotations" \
     "
-!com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+!com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
 * # All other classes allowed
 "
 
@@ -225,11 +215,7 @@
 * # All other classes allowed
 "
 
-STUB="" run_hoststubgen_for_success "No stub generation" ""
-
-IMPL="" run_hoststubgen_for_success "No impl generation" ""
-
-STUB="" IMPL="" run_hoststubgen_for_success "No stub, no impl generation" ""
+OUTJAR="" run_hoststubgen_for_success "No output generation" ""
 
 EXTRA_ARGS="--in-jar abc" run_hoststubgen_for_failure "Duplicate arg" \
     "Duplicate or conflicting argument found: --in-jar" \
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 7b08678..0f38fe7 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -24,19 +24,13 @@
 import com.android.hoststubgen.filters.FilterPolicy
 import com.android.hoststubgen.filters.FilterRemapper
 import com.android.hoststubgen.filters.ImplicitOutputFilter
+import com.android.hoststubgen.filters.NativeFilter
 import com.android.hoststubgen.filters.OutputFilter
-import com.android.hoststubgen.filters.StubIntersectingFilter
 import com.android.hoststubgen.filters.createFilterFromTextPolicyFile
 import com.android.hoststubgen.filters.printAsTextPolicy
 import com.android.hoststubgen.utils.ClassFilter
 import com.android.hoststubgen.visitors.BaseAdapter
 import com.android.hoststubgen.visitors.PackageRedirectRemapper
-import org.objectweb.asm.ClassReader
-import org.objectweb.asm.ClassVisitor
-import org.objectweb.asm.ClassWriter
-import org.objectweb.asm.commons.ClassRemapper
-import org.objectweb.asm.commons.Remapper
-import org.objectweb.asm.util.CheckClassAdapter
 import java.io.BufferedInputStream
 import java.io.BufferedOutputStream
 import java.io.FileOutputStream
@@ -46,6 +40,12 @@
 import java.util.zip.ZipEntry
 import java.util.zip.ZipFile
 import java.util.zip.ZipOutputStream
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.commons.ClassRemapper
+import org.objectweb.asm.commons.Remapper
+import org.objectweb.asm.util.CheckClassAdapter
 
 /**
  * Actual main class.
@@ -82,17 +82,16 @@
 
         // Transform the jar.
         convert(
-                options.inJar.get,
-                options.outStubJar.get,
-                options.outImplJar.get,
-                filter,
-                options.enableClassChecker.get,
-                allClasses,
-                errors,
-                stats,
-                filterRemapper,
-                options.numShards.get,
-                options.shard.get,
+            options.inJar.get,
+            options.outJar.get,
+            filter,
+            options.enableClassChecker.get,
+            allClasses,
+            errors,
+            stats,
+            filterRemapper,
+            options.numShards.get,
+            options.shard.get,
         )
 
         // Dump statistics, if specified.
@@ -117,10 +116,10 @@
      * jars, and "how". (e.g. with substitution?)
      */
     private fun buildFilter(
-            errors: HostStubGenErrors,
-            allClasses: ClassNodes,
-            options: HostStubGenOptions,
-            ): OutputFilter {
+        errors: HostStubGenErrors,
+        allClasses: ClassNodes,
+        options: HostStubGenOptions,
+    ): OutputFilter {
         // We build a "chain" of multiple filters here.
         //
         // The filters are build in from "inside", meaning the first filter created here is
@@ -134,6 +133,9 @@
         // The first filter is for the default policy from the command line options.
         var filter: OutputFilter = ConstantFilter(options.defaultPolicy.get, "default-by-options")
 
+        // Next, we build a filter that preserves all native methods by default
+        filter = NativeFilter(allClasses, filter)
+
         // Next, we need a filter that resolves "class-wide" policies.
         // This is used when a member (methods, fields, nested classes) don't get any polices
         // from upper filters. e.g. when a method has no annotations, then this filter will apply
@@ -159,9 +161,7 @@
         filter = AnnotationBasedFilter(
             errors,
             allClasses,
-            options.stubAnnotations,
             options.keepAnnotations,
-            options.stubClassAnnotations,
             options.keepClassAnnotations,
             options.throwAnnotations,
             options.removeAnnotations,
@@ -179,15 +179,6 @@
             filter = createFilterFromTextPolicyFile(it, allClasses, filter)
         }
 
-        // If `--intersect-stub-jar` is provided, load from these jar files too.
-        // We use this to restrict stub APIs to public/system/test APIs,
-        // by intersecting with a stub jar file created by metalava.
-        if (options.intersectStubJars.size > 0) {
-            val intersectingJars = loadIntersectingJars(options.intersectStubJars)
-
-            filter = StubIntersectingFilter(errors, intersectingJars, filter)
-        }
-
         // Apply the implicit filter.
         filter = ImplicitOutputFilter(errors, allClasses, filter)
 
@@ -195,34 +186,21 @@
     }
 
     /**
-     * Load jar files specified with "--intersect-stub-jar".
-     */
-    private fun loadIntersectingJars(filenames: Set<String>): Map<String, ClassNodes> {
-        val intersectingJars = mutableMapOf<String, ClassNodes>()
-
-        filenames.forEach { filename ->
-            intersectingJars[filename] = ClassNodes.loadClassStructures(filename)
-        }
-        return intersectingJars
-    }
-
-    /**
      * Convert a JAR file into "stub" and "impl" JAR files.
      */
     private fun convert(
-            inJar: String,
-            outStubJar: String?,
-            outImplJar: String?,
-            filter: OutputFilter,
-            enableChecker: Boolean,
-            classes: ClassNodes,
-            errors: HostStubGenErrors,
-            stats: HostStubGenStats,
-            remapper: Remapper?,
-            numShards: Int,
-            shard: Int,
-            ) {
-        log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar)
+        inJar: String,
+        outJar: String?,
+        filter: OutputFilter,
+        enableChecker: Boolean,
+        classes: ClassNodes,
+        errors: HostStubGenErrors,
+        stats: HostStubGenStats,
+        remapper: Remapper?,
+        numShards: Int,
+        shard: Int
+    ) {
+        log.i("Converting %s into %s ...", inJar, outJar)
         log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
 
         log.iTime("Transforming jar") {
@@ -240,29 +218,26 @@
                     val shardStart = numItems * shard / numShards
                     val shardNextStart = numItems * (shard + 1) / numShards
 
-                    maybeWithZipOutputStream(outStubJar) { stubOutStream ->
-                        maybeWithZipOutputStream(outImplJar) { implOutStream ->
-                            val inEntries = inZip.entries()
-                            while (inEntries.hasMoreElements()) {
-                                val entry = inEntries.nextElement()
-                                val inShard = (shardStart <= itemIndex)
-                                        && (itemIndex < shardNextStart)
-                                itemIndex++
-                                if (!inShard) {
-                                    continue
-                                }
-                                convertSingleEntry(
-                                    inZip, entry, stubOutStream, implOutStream,
-                                    filter, packageRedirector, remapper,
-                                    enableChecker, classes, errors, stats
-                                )
-                                numItemsProcessed++
+                    maybeWithZipOutputStream(outJar) { outStream ->
+                        val inEntries = inZip.entries()
+                        while (inEntries.hasMoreElements()) {
+                            val entry = inEntries.nextElement()
+                            val inShard = (shardStart <= itemIndex)
+                                    && (itemIndex < shardNextStart)
+                            itemIndex++
+                            if (!inShard) {
+                                continue
                             }
-                            log.i("Converted all entries.")
+                            convertSingleEntry(
+                                inZip, entry, outStream, filter,
+                                packageRedirector, remapper, enableChecker,
+                                classes, errors, stats
+                            )
+                            numItemsProcessed++
                         }
+                        log.i("Converted all entries.")
                     }
-                    outStubJar?.let { log.i("Created stub: $it") }
-                    outImplJar?.let { log.i("Created impl: $it") }
+                    outJar?.let { log.i("Created: $it") }
                 }
             }
             log.i("%d / %d item(s) processed.", numItemsProcessed, numItems)
@@ -280,18 +255,17 @@
      * Convert a single ZIP entry, which may or may not be a class file.
      */
     private fun convertSingleEntry(
-            inZip: ZipFile,
-            entry: ZipEntry,
-            stubOutStream: ZipOutputStream?,
-            implOutStream: ZipOutputStream?,
-            filter: OutputFilter,
-            packageRedirector: PackageRedirectRemapper,
-            remapper: Remapper?,
-            enableChecker: Boolean,
-            classes: ClassNodes,
-            errors: HostStubGenErrors,
-            stats: HostStubGenStats,
-            ) {
+        inZip: ZipFile,
+        entry: ZipEntry,
+        outStream: ZipOutputStream?,
+        filter: OutputFilter,
+        packageRedirector: PackageRedirectRemapper,
+        remapper: Remapper?,
+        enableChecker: Boolean,
+        classes: ClassNodes,
+        errors: HostStubGenErrors,
+        stats: HostStubGenStats
+    ) {
         log.d("Entry: %s", entry.name)
         log.withIndent {
             val name = entry.name
@@ -303,8 +277,10 @@
 
             // If it's a class, convert it.
             if (name.endsWith(".class")) {
-                processSingleClass(inZip, entry, stubOutStream, implOutStream, filter,
-                        packageRedirector, remapper, enableChecker, classes, errors, stats)
+                processSingleClass(
+                    inZip, entry, outStream, filter, packageRedirector,
+                    remapper, enableChecker, classes, errors, stats
+                )
                 return
             }
 
@@ -312,17 +288,14 @@
 
             // - *.uau seems to contain hidden API information.
             // -  *_compat_config.xml is also about compat-framework.
-            if (name.endsWith(".uau") ||
-                    name.endsWith("_compat_config.xml")) {
+            if (name.endsWith(".uau") || name.endsWith("_compat_config.xml")) {
                 log.d("Not needed: %s", entry.name)
                 return
             }
 
             // Unknown type, we just copy it to both output zip files.
-            // TODO: We probably shouldn't do it for stub jar?
             log.v("Copying: %s", entry.name)
-            stubOutStream?.let { copyZipEntry(inZip, entry, it) }
-            implOutStream?.let { copyZipEntry(inZip, entry, it) }
+            outStream?.let { copyZipEntry(inZip, entry, it) }
         }
     }
 
@@ -330,10 +303,10 @@
      * Copy a single ZIP entry to the output.
      */
     private fun copyZipEntry(
-            inZip: ZipFile,
-            entry: ZipEntry,
-            out: ZipOutputStream,
-            ) {
+        inZip: ZipFile,
+        entry: ZipEntry,
+        out: ZipOutputStream,
+    ) {
         // TODO: It seems like copying entries this way is _very_ slow,
         // even with out.setLevel(0). Look for other ways to do it.
 
@@ -350,18 +323,17 @@
      * Convert a single class to "stub" and "impl".
      */
     private fun processSingleClass(
-            inZip: ZipFile,
-            entry: ZipEntry,
-            stubOutStream: ZipOutputStream?,
-            implOutStream: ZipOutputStream?,
-            filter: OutputFilter,
-            packageRedirector: PackageRedirectRemapper,
-            remapper: Remapper?,
-            enableChecker: Boolean,
-            classes: ClassNodes,
-            errors: HostStubGenErrors,
-            stats: HostStubGenStats,
-            ) {
+        inZip: ZipFile,
+        entry: ZipEntry,
+        outStream: ZipOutputStream?,
+        filter: OutputFilter,
+        packageRedirector: PackageRedirectRemapper,
+        remapper: Remapper?,
+        enableChecker: Boolean,
+        classes: ClassNodes,
+        errors: HostStubGenErrors,
+        stats: HostStubGenStats
+    ) {
         val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "")
         val classPolicy = filter.getPolicyForClass(classInternalName)
         if (classPolicy.policy == FilterPolicy.Remove) {
@@ -373,33 +345,22 @@
         remapper?.mapType(classInternalName)?.let { remappedName ->
             if (remappedName != classInternalName) {
                 log.d("Renaming class file: %s -> %s", classInternalName, remappedName)
-                newName = remappedName + ".class"
+                newName = "$remappedName.class"
             }
         }
-        // Generate stub first.
-        if (stubOutStream != null && classPolicy.policy.needsInStub) {
-            log.v("Creating stub class: %s Policy: %s", classInternalName, classPolicy)
+
+        if (outStream != null) {
+            log.v("Creating class: %s Policy: %s", classInternalName, classPolicy)
             log.withIndent {
                 BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
                     val newEntry = ZipEntry(newName)
-                    stubOutStream.putNextEntry(newEntry)
-                    convertClass(classInternalName, /*forImpl=*/false, bis,
-                            stubOutStream, filter, packageRedirector, remapper,
-                            enableChecker, classes, errors, null)
-                    stubOutStream.closeEntry()
-                }
-            }
-        }
-        if (implOutStream != null && classPolicy.policy.needsInImpl) {
-            log.v("Creating impl class: %s Policy: %s", classInternalName, classPolicy)
-            log.withIndent {
-                BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
-                    val newEntry = ZipEntry(newName)
-                    implOutStream.putNextEntry(newEntry)
-                    convertClass(classInternalName, /*forImpl=*/true, bis,
-                            implOutStream, filter, packageRedirector, remapper,
-                            enableChecker, classes, errors, stats)
-                    implOutStream.closeEntry()
+                    outStream.putNextEntry(newEntry)
+                    convertClass(
+                        classInternalName, bis,
+                        outStream, filter, packageRedirector, remapper,
+                        enableChecker, classes, errors, stats
+                    )
+                    outStream.closeEntry()
                 }
             }
         }
@@ -409,18 +370,17 @@
      * Convert a single class to either "stub" or "impl".
      */
     private fun convertClass(
-            classInternalName: String,
-            forImpl: Boolean,
-            input: InputStream,
-            out: OutputStream,
-            filter: OutputFilter,
-            packageRedirector: PackageRedirectRemapper,
-            remapper: Remapper?,
-            enableChecker: Boolean,
-            classes: ClassNodes,
-            errors: HostStubGenErrors,
-            stats: HostStubGenStats?,
-            ) {
+        classInternalName: String,
+        input: InputStream,
+        out: OutputStream,
+        filter: OutputFilter,
+        packageRedirector: PackageRedirectRemapper,
+        remapper: Remapper?,
+        enableChecker: Boolean,
+        classes: ClassNodes,
+        errors: HostStubGenErrors,
+        stats: HostStubGenStats?
+    ) {
         val cr = ClassReader(input)
 
         // COMPUTE_FRAMES wouldn't be happy if code uses
@@ -439,14 +399,15 @@
         }
 
         val visitorOptions = BaseAdapter.Options(
-                enablePreTrace = options.enablePreTrace.get,
-                enablePostTrace = options.enablePostTrace.get,
-                enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection.get,
-                errors = errors,
-                stats = stats,
+            errors = errors,
+            stats = stats,
+            enablePreTrace = options.enablePreTrace.get,
+            enablePostTrace = options.enablePostTrace.get,
         )
-        outVisitor = BaseAdapter.getVisitor(classInternalName, classes, outVisitor, filter,
-                packageRedirector, remapper, forImpl, visitorOptions)
+        outVisitor = BaseAdapter.getVisitor(
+            classInternalName, classes, outVisitor, filter,
+            packageRedirector, visitorOptions
+        )
 
         cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
         val data = cw.toByteArray()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index f88b107..1cedcc3 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -74,21 +74,16 @@
         /** Input jar file*/
         var inJar: SetOnce<String> = SetOnce(""),
 
-        /** Output stub jar file */
-        var outStubJar: SetOnce<String?> = SetOnce(null),
-
-        /** Output implementation jar file */
-        var outImplJar: SetOnce<String?> = SetOnce(null),
+        /** Output jar file */
+        var outJar: SetOnce<String?> = SetOnce(null),
 
         var inputJarDumpFile: SetOnce<String?> = SetOnce(null),
 
         var inputJarAsKeepAllFile: SetOnce<String?> = SetOnce(null),
 
-        var stubAnnotations: MutableSet<String> = mutableSetOf(),
         var keepAnnotations: MutableSet<String> = mutableSetOf(),
         var throwAnnotations: MutableSet<String> = mutableSetOf(),
         var removeAnnotations: MutableSet<String> = mutableSetOf(),
-        var stubClassAnnotations: MutableSet<String> = mutableSetOf(),
         var keepClassAnnotations: MutableSet<String> = mutableSetOf(),
 
         var substituteAnnotations: MutableSet<String> = mutableSetOf(),
@@ -103,8 +98,6 @@
         var defaultClassLoadHook: SetOnce<String?> = SetOnce(null),
         var defaultMethodCallHook: SetOnce<String?> = SetOnce(null),
 
-        var intersectStubJars: MutableSet<String> = mutableSetOf(),
-
         var policyOverrideFile: SetOnce<String?> = SetOnce(null),
 
         var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove),
@@ -115,8 +108,6 @@
         var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
         var enablePostTrace: SetOnce<Boolean> = SetOnce(false),
 
-        var enableNonStubMethodCallDetection: SetOnce<Boolean> = SetOnce(false),
-
         var statsFile: SetOnce<String?> = SetOnce(null),
 
         var apiListFile: SetOnce<String?> = SetOnce(null),
@@ -150,10 +141,7 @@
             }
 
             while (true) {
-                val arg = ai.nextArgOptional()
-                if (arg == null) {
-                    break
-                }
+                val arg = ai.nextArgOptional() ?: break
 
                 // Define some shorthands...
                 fun nextArg(): String = ai.nextArgRequired(arg)
@@ -169,8 +157,9 @@
                         "-h", "--help" -> TODO("Help is not implemented yet")
 
                         "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
-                        "--out-stub-jar" -> ret.outStubJar.set(nextArg())
-                        "--out-impl-jar" -> ret.outImplJar.set(nextArg())
+                        // We support both arguments because some AOSP dependencies
+                        // still use the old argument
+                        "--out-jar", "--out-impl-jar" -> ret.outJar.set(nextArg())
 
                         "--policy-override-file" ->
                             ret.policyOverrideFile.set(nextArg())!!.ensureFileExists()
@@ -181,17 +170,10 @@
                         "--default-remove" -> ret.defaultPolicy.set(FilterPolicy.Remove)
                         "--default-throw" -> ret.defaultPolicy.set(FilterPolicy.Throw)
                         "--default-keep" -> ret.defaultPolicy.set(FilterPolicy.Keep)
-                        "--default-stub" -> ret.defaultPolicy.set(FilterPolicy.Stub)
-
-                        "--stub-annotation" ->
-                            ret.stubAnnotations.addUniqueAnnotationArg()
 
                         "--keep-annotation" ->
                             ret.keepAnnotations.addUniqueAnnotationArg()
 
-                        "--stub-class-annotation" ->
-                            ret.stubClassAnnotations.addUniqueAnnotationArg()
-
                         "--keep-class-annotation" ->
                             ret.keepClassAnnotations.addUniqueAnnotationArg()
 
@@ -225,9 +207,6 @@
                         "--default-method-call-hook" ->
                             ret.defaultMethodCallHook.set(nextArg())
 
-                        "--intersect-stub-jar" ->
-                            ret.intersectStubJars += nextArg().ensureFileExists()
-
                         "--gen-keep-all-file" ->
                             ret.inputJarAsKeepAllFile.set(nextArg())
 
@@ -241,12 +220,6 @@
                         "--enable-post-trace" -> ret.enablePostTrace.set(true)
                         "--no-post-trace" -> ret.enablePostTrace.set(false)
 
-                        "--enable-non-stub-method-check" ->
-                            ret.enableNonStubMethodCallDetection.set(true)
-
-                        "--no-non-stub-method-check" ->
-                            ret.enableNonStubMethodCallDetection.set(false)
-
                         "--gen-input-dump-file" -> ret.inputJarDumpFile.set(nextArg())
 
                         "--stats-file" -> ret.statsFile.set(nextArg())
@@ -273,9 +246,8 @@
             if (!ret.inJar.isSet) {
                 throw ArgumentsException("Required option missing: --in-jar")
             }
-            if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) {
-                log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
-                        " $executableName will not generate jar files.")
+            if (!ret.outJar.isSet) {
+                log.w("--out-jar is not set. $executableName will not generate jar files.")
             }
             if (ret.numShards.isSet != ret.shard.isSet) {
                 throw ArgumentsException("--num-shards and --shard-index must be used together")
@@ -287,11 +259,6 @@
                 }
             }
 
-            if (ret.enableNonStubMethodCallDetection.get) {
-                log.w("--enable-non-stub-method-check is not fully implemented yet." +
-                    " See the todo in doesMethodNeedNonStubCallCheck().")
-            }
-
             return ret
         }
     }
@@ -300,32 +267,27 @@
         return """
             HostStubGenOptions{
               inJar='$inJar',
-              outStubJar='$outStubJar',
-              outImplJar='$outImplJar',
+              outJar='$outJar',
               inputJarDumpFile=$inputJarDumpFile,
               inputJarAsKeepAllFile=$inputJarAsKeepAllFile,
-              stubAnnotations=$stubAnnotations,
               keepAnnotations=$keepAnnotations,
               throwAnnotations=$throwAnnotations,
               removeAnnotations=$removeAnnotations,
-              stubClassAnnotations=$stubClassAnnotations,
               keepClassAnnotations=$keepClassAnnotations,
               substituteAnnotations=$substituteAnnotations,
               nativeSubstituteAnnotations=$nativeSubstituteAnnotations,
               classLoadHookAnnotations=$classLoadHookAnnotations,
               keepStaticInitializerAnnotations=$keepStaticInitializerAnnotations,
               packageRedirects=$packageRedirects,
-              $annotationAllowedClassesFile=$annotationAllowedClassesFile,
+              annotationAllowedClassesFile=$annotationAllowedClassesFile,
               defaultClassLoadHook=$defaultClassLoadHook,
               defaultMethodCallHook=$defaultMethodCallHook,
-              intersectStubJars=$intersectStubJars,
               policyOverrideFile=$policyOverrideFile,
               defaultPolicy=$defaultPolicy,
               cleanUpOnError=$cleanUpOnError,
               enableClassChecker=$enableClassChecker,
               enablePreTrace=$enablePreTrace,
               enablePostTrace=$enablePostTrace,
-              enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection,
               statsFile=$statsFile,
               apiListFile=$apiListFile,
               numShards=$numShards,
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 6cf2143..7197e0e 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -202,18 +202,6 @@
 }
 
 /**
- * Take a class name. If it's a nested class, then return the name of its direct outer class name.
- * Otherwise, return null.
- */
-fun getDirectOuterClassName(className: String): String? {
-    val pos = className.lastIndexOf('$')
-    if (pos < 0) {
-        return null
-    }
-    return className.substring(0, pos)
-}
-
-/**
  * Write bytecode to push all the method arguments to the stack.
  * The number of arguments and their type are taken from [methodDescriptor].
  */
@@ -339,6 +327,10 @@
     return (this.access and Opcodes.ACC_PUBLIC) != 0
 }
 
+fun MethodNode.isNative(): Boolean {
+    return (this.access and Opcodes.ACC_NATIVE) != 0
+}
+
 fun MethodNode.isSpecial(): Boolean {
     return CTOR_NAME == this.name || CLASS_INITIALIZER_NAME == this.name
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
index aaefee4..5e4e70f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
@@ -44,7 +44,7 @@
         val descriptor: String,
     )
 
-    val javaStandardApiPolicy = FilterPolicy.Stub.withReason("Java standard API")
+    private val javaStandardApiPolicy = FilterPolicy.Keep.withReason("Java standard API")
 
     private val shownMethods = mutableSetOf<MethodKey>()
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
index 248121c..38a41b2 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
@@ -17,7 +17,6 @@
 
 import com.android.hoststubgen.ClassParseException
 import com.android.hoststubgen.HostStubGenErrors
-import com.android.hoststubgen.HostStubGenInternalException
 import com.android.hoststubgen.InvalidAnnotationException
 import com.android.hoststubgen.addNonNullElement
 import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
@@ -35,258 +34,125 @@
 
 // TODO: Detect invalid cases, such as...
 // - Class's visibility is lower than the members'.
-// - HostSideTestSubstituteWith is set, but it doesn't have @Stub or @Keep
 
 /**
  * [OutputFilter] using Java annotations.
  */
 class AnnotationBasedFilter(
-        private val errors: HostStubGenErrors,
-        private val classes: ClassNodes,
-        stubAnnotations_: Set<String>,
-        keepAnnotations_: Set<String>,
-        stubClassAnnotations_: Set<String>,
-        keepClassAnnotations_: Set<String>,
-        throwAnnotations_: Set<String>,
-        removeAnnotations_: Set<String>,
-        substituteAnnotations_: Set<String>,
-        nativeSubstituteAnnotations_: Set<String>,
-        classLoadHookAnnotations_: Set<String>,
-        keepStaticInitializerAnnotations_: Set<String>,
-        private val annotationAllowedClassesFilter: ClassFilter,
-        fallback: OutputFilter,
+    private val errors: HostStubGenErrors,
+    private val classes: ClassNodes,
+    keepAnnotations_: Set<String>,
+    keepClassAnnotations_: Set<String>,
+    throwAnnotations_: Set<String>,
+    removeAnnotations_: Set<String>,
+    substituteAnnotations_: Set<String>,
+    nativeSubstituteAnnotations_: Set<String>,
+    classLoadHookAnnotations_: Set<String>,
+    keepStaticInitializerAnnotations_: Set<String>,
+    private val annotationAllowedClassesFilter: ClassFilter,
+    fallback: OutputFilter,
 ) : DelegatingFilter(fallback) {
-    private var stubAnnotations = convertToInternalNames(stubAnnotations_)
-    private var keepAnnotations = convertToInternalNames(keepAnnotations_)
-    private var stubClassAnnotations = convertToInternalNames(stubClassAnnotations_)
-    private var keepClassAnnotations = convertToInternalNames(keepClassAnnotations_)
-    private var throwAnnotations = convertToInternalNames(throwAnnotations_)
-    private var removeAnnotations = convertToInternalNames(removeAnnotations_)
-    private var substituteAnnotations = convertToInternalNames(substituteAnnotations_)
-    private var nativeSubstituteAnnotations = convertToInternalNames(nativeSubstituteAnnotations_)
-    private var classLoadHookAnnotations = convertToInternalNames(classLoadHookAnnotations_)
-    private var keepStaticInitializerAnnotations =
-            convertToInternalNames(keepStaticInitializerAnnotations_)
+    private val keepAnnotations = convertToInternalNames(keepAnnotations_)
+    private val keepClassAnnotations = convertToInternalNames(keepClassAnnotations_)
+    private val throwAnnotations = convertToInternalNames(throwAnnotations_)
+    private val removeAnnotations = convertToInternalNames(removeAnnotations_)
+    private val substituteAnnotations = convertToInternalNames(substituteAnnotations_)
+    private val nativeSubstituteAnnotations = convertToInternalNames(nativeSubstituteAnnotations_)
+    private val classLoadHookAnnotations = convertToInternalNames(classLoadHookAnnotations_)
+    private val keepStaticInitializerAnnotations =
+        convertToInternalNames(keepStaticInitializerAnnotations_)
 
     /** Annotations that control API visibility. */
-    private var visibilityAnnotations: Set<String> = convertToInternalNames(
-        stubAnnotations_ +
-        keepAnnotations_ +
-        stubClassAnnotations_ +
-        keepClassAnnotations_ +
-        throwAnnotations_ +
-        removeAnnotations_)
+    private val visibilityAnnotations = keepAnnotations +
+            keepClassAnnotations +
+            throwAnnotations +
+            removeAnnotations +
+            substituteAnnotations
+
+    /** All the annotations we use. */
+    private val allAnnotations = visibilityAnnotations +
+            nativeSubstituteAnnotations +
+            classLoadHookAnnotations +
+            keepStaticInitializerAnnotations
 
     /**
      * All the annotations we use. Note, this one is in a [convertToJvmNames] format unlike
      * other ones, because of how it's used.
      */
-    private var allAnnotations: Set<String> = convertToJvmNames(
-        stubAnnotations_ +
-                keepAnnotations_ +
-                stubClassAnnotations_ +
+    private val allAnnotationClasses: Set<String> = convertToJvmNames(
+        keepAnnotations_ +
                 keepClassAnnotations_ +
                 throwAnnotations_ +
                 removeAnnotations_ +
                 substituteAnnotations_ +
                 nativeSubstituteAnnotations_ +
-                classLoadHookAnnotations_)
+                classLoadHookAnnotations_ +
+                keepStaticInitializerAnnotations_
+    )
 
-    private val substitutionHelper = SubstitutionHelper()
+    private val policyCache = mutableMapOf<String, ClassAnnotations>()
 
-    private val reasonAnnotation = "annotation"
-    private val reasonClassAnnotation = "class-annotation"
-
-    /**
-     * Throw if an item has more than one visibility annotations.
-     *
-     * name1 - 4 are only used in exception messages. We take them as separate strings
-     * to avoid unnecessary string concatenations.
-     */
-    private fun detectInvalidAnnotations(
-        visibles: List<AnnotationNode>?,
-        invisibles: List<AnnotationNode>?,
-        type: String,
-        name1: String,
-        name2: String,
-        name3: String,
-    ) {
-        var count = 0
-        for (an in visibles ?: emptyList()) {
-            if (visibilityAnnotations.contains(an.desc)) {
-                count++
-            }
-        }
-        for (an in invisibles ?: emptyList()) {
-            if (visibilityAnnotations.contains(an.desc)) {
-                count++
-            }
-        }
-        if (count > 1) {
-            val description = if (name2 == "" && name3 == "") {
-                "$type $name1"
-            } else {
-                "$type $name1.$name2$name3"
-            }
-            throw InvalidAnnotationException(
-                "Found more than one visibility annotations on $description")
+    private val AnnotationNode.policy: FilterPolicyWithReason? get() {
+        return when (desc) {
+            in keepAnnotations -> FilterPolicy.Keep.withReason(REASON_ANNOTATION)
+            in keepClassAnnotations -> FilterPolicy.KeepClass.withReason(REASON_CLASS_ANNOTATION)
+            in substituteAnnotations -> FilterPolicy.Substitute.withReason(REASON_ANNOTATION)
+            in throwAnnotations -> FilterPolicy.Throw.withReason(REASON_ANNOTATION)
+            in removeAnnotations -> FilterPolicy.Remove.withReason(REASON_ANNOTATION)
+            else -> null
         }
     }
 
-    fun findAnyAnnotation(
-            className: String,
-            anyAnnotations: Set<String>,
-            visibleAnnotations: List<AnnotationNode>?,
-            invisibleAnnotations: List<AnnotationNode>?,
-    ): AnnotationNode? {
-        val ret = findAnyAnnotation(anyAnnotations, visibleAnnotations, invisibleAnnotations)
-
-        if (ret != null) {
-            if (!annotationAllowedClassesFilter.matches(className)) {
-                throw InvalidAnnotationException(
-                        "Class ${className.toHumanReadableClassName()} is not allowed to have " +
-                                "Ravenwood annotations. Contact g/ravenwood for more details.")
-            }
-        }
-
-        return ret
-    }
-
-    /**
-     * Find a visibility annotation.
-     *
-     * name1 - 4 are only used in exception messages.
-     */
-    private fun findAnnotation(
-            className: String,
-            visibles: List<AnnotationNode>?,
-            invisibles: List<AnnotationNode>?,
-            type: String,
-            name1: String,
-            name2: String = "",
-            name3: String = "",
-    ): FilterPolicyWithReason? {
-        detectInvalidAnnotations(visibles, invisibles, type, name1, name2, name3)
-
-        findAnyAnnotation(className, stubAnnotations, visibles, invisibles)?.let {
-            return FilterPolicy.Stub.withReason(reasonAnnotation)
-        }
-        findAnyAnnotation(className, stubClassAnnotations, visibles, invisibles)?.let {
-            return FilterPolicy.StubClass.withReason(reasonClassAnnotation)
-        }
-        findAnyAnnotation(className, keepAnnotations, visibles, invisibles)?.let {
-            return FilterPolicy.Keep.withReason(reasonAnnotation)
-        }
-        findAnyAnnotation(className, keepClassAnnotations, visibles, invisibles)?.let {
-            return FilterPolicy.KeepClass.withReason(reasonClassAnnotation)
-        }
-        findAnyAnnotation(className, throwAnnotations, visibles, invisibles)?.let {
-            return FilterPolicy.Throw.withReason(reasonAnnotation)
-        }
-        findAnyAnnotation(className, removeAnnotations, visibles, invisibles)?.let {
-            return FilterPolicy.Remove.withReason(reasonAnnotation)
-        }
-
-        return null
+    private fun getAnnotationPolicy(cn: ClassNode): ClassAnnotations {
+        return policyCache.getOrPut(cn.name) { ClassAnnotations(cn) }
     }
 
     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
-        val cn = classes.getClass(className)
-
-        findAnnotation(
-            cn.name,
-            cn.visibleAnnotations,
-            cn.invisibleAnnotations,
-            "class",
-            className)?.let {
-            return it
-        }
-
         // If it's any of the annotations, then always keep it.
-        if (allAnnotations.contains(className)) {
+        if (allAnnotationClasses.contains(className)) {
             return FilterPolicy.KeepClass.withReason("HostStubGen Annotation")
         }
 
-        return super.getPolicyForClass(className)
+        val cn = classes.getClass(className)
+        return getAnnotationPolicy(cn).classPolicy ?: super.getPolicyForClass(className)
     }
 
-    override fun getPolicyForField(
-            className: String,
-            fieldName: String
-    ): FilterPolicyWithReason {
+    override fun getPolicyForField(className: String, fieldName: String): FilterPolicyWithReason {
         val cn = classes.getClass(className)
-
-        cn.fields?.firstOrNull { it.name == fieldName }?.let {fn ->
-            findAnnotation(
-                cn.name,
-                fn.visibleAnnotations,
-                fn.invisibleAnnotations,
-                "field",
-                className,
-                fieldName
-                )?.let { policy ->
-                // If the item has an annotation, then use it.
-                return policy
-            }
-        }
-        return super.getPolicyForField(className, fieldName)
+        return getAnnotationPolicy(cn).fieldPolicies[fieldName]
+            ?: super.getPolicyForField(className, fieldName)
     }
 
     override fun getPolicyForMethod(
-            className: String,
-            methodName: String,
-            descriptor: String
+        className: String,
+        methodName: String,
+        descriptor: String
     ): FilterPolicyWithReason {
         val cn = classes.getClass(className)
 
         if (methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) {
-            findAnyAnnotation(cn.name, keepStaticInitializerAnnotations,
-                    cn.visibleAnnotations, cn.invisibleAnnotations)?.let {
-                return FilterPolicy.Keep.withReason(reasonAnnotation)
+            if (cn.findAnyAnnotation(keepStaticInitializerAnnotations) != null) {
+                return FilterPolicy.Keep.withReason(REASON_ANNOTATION)
             }
         }
 
-        cn.methods?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
-            // @SubstituteWith is going to complicate the policy here, so we ask helper
-            // what to do.
-            substitutionHelper.getPolicyFromSubstitution(cn, mn.name, mn.desc)?.let {
-                return it
-            }
-
-            // If there's no substitution, then we check the annotation.
-            findAnnotation(
-                cn.name,
-                mn.visibleAnnotations,
-                mn.invisibleAnnotations,
-                "method",
-                className,
-                methodName,
-                descriptor
-            )?.let { policy ->
-                return policy
-            }
-        }
-        return super.getPolicyForMethod(className, methodName, descriptor)
+        return getAnnotationPolicy(cn).methodPolicies[MethodKey(methodName, descriptor)]
+            ?: super.getPolicyForMethod(className, methodName, descriptor)
     }
 
     override fun getRenameTo(
-            className: String,
-            methodName: String,
-            descriptor: String
+        className: String,
+        methodName: String,
+        descriptor: String
     ): String? {
         val cn = classes.getClass(className)
-
-        // If the method has a "substitute with" annotation, then return its "value" parameter.
-        cn.methods?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
-            return substitutionHelper.getRenameTo(cn, mn.name, mn.desc)
-        }
-        return null
+        return getAnnotationPolicy(cn).renamedMethods[MethodKey(methodName, descriptor)]
+            ?: super.getRenameTo(className, methodName, descriptor)
     }
 
     override fun getNativeSubstitutionClass(className: String): String? {
         classes.getClass(className).let { cn ->
-            findAnyAnnotation(nativeSubstituteAnnotations,
-                    cn.visibleAnnotations, cn.invisibleAnnotations)?.let { an ->
+            cn.findAnyAnnotation(nativeSubstituteAnnotations)?.let { an ->
                 return getAnnotationField(an, "value")?.toJvmClassName()
             }
         }
@@ -295,8 +161,7 @@
 
     override fun getClassLoadHooks(className: String): List<String> {
         val e = classes.getClass(className).let { cn ->
-            findAnyAnnotation(classLoadHookAnnotations,
-                cn.visibleAnnotations, cn.invisibleAnnotations)?.let { an ->
+            cn.findAnyAnnotation(classLoadHookAnnotations)?.let { an ->
                 getAnnotationField(an, "value")?.toHumanReadableMethodName()
             }
         }
@@ -306,102 +171,130 @@
     private data class MethodKey(val name: String, val desc: String)
 
     /**
-     * In order to handle substitution, we need to build a reverse mapping of substitution
-     * methods.
+     * Every time we see a class, we scan all its methods for substitution attributes,
+     * and compute (implicit) policies caused by them.
      *
-     * This class automatically builds such a map internally that the above methods can
-     * take advantage of.
+     * For example, for the following methods:
+     *
+     *   @Substitute(suffix = "_host")
+     *   private void foo() {
+     *      // This isn't supported on the host side.
+     *   }
+     *   private void foo_host() {
+     *      // Host side implementation
+     *   }
+     *
+     * We internally handle them as:
+     *
+     *   foo() -> Substitute
+     *   foo_host() -> Stub, and then rename it to foo().
      */
-    private inner class SubstitutionHelper {
-        private var currentClass: ClassNode? = null
+    private inner class ClassAnnotations(cn: ClassNode) {
 
-        private var policiesFromSubstitution = mutableMapOf<MethodKey, FilterPolicyWithReason>()
-        private var substituteToMethods = mutableMapOf<MethodKey, String>()
+        val classPolicy: FilterPolicyWithReason?
+        val fieldPolicies = mutableMapOf<String, FilterPolicyWithReason>()
+        val methodPolicies = mutableMapOf<MethodKey, FilterPolicyWithReason>()
+        val renamedMethods = mutableMapOf<MethodKey, String>()
 
-        fun getPolicyFromSubstitution(cn: ClassNode, methodName: String, descriptor: String):
-                FilterPolicyWithReason? {
-            setClass(cn)
-            return policiesFromSubstitution[MethodKey(methodName, descriptor)]
-        }
+        init {
+            val allowAnnotation = annotationAllowedClassesFilter.matches(cn.name)
+            detectInvalidAnnotations(
+                cn.name, allowAnnotation,
+                cn.visibleAnnotations, cn.invisibleAnnotations,
+                "class", cn.name
+            )
+            classPolicy = cn.findAnyAnnotation(visibilityAnnotations)?.policy
 
-        fun getRenameTo(cn: ClassNode, methodName: String, descriptor: String): String? {
-            setClass(cn)
-            return substituteToMethods[MethodKey(methodName, descriptor)]
+            for (fn in cn.fields ?: emptyList()) {
+                detectInvalidAnnotations(
+                    cn.name, allowAnnotation,
+                    fn.visibleAnnotations, fn.invisibleAnnotations,
+                    "field", cn.name, fn.name
+                )
+                fn.findAnyAnnotation(visibilityAnnotations)?.policy?.let {
+                    fieldPolicies[fn.name] = it
+                }
+            }
+
+            for (mn in cn.methods ?: emptyList()) {
+                detectInvalidAnnotations(
+                    cn.name, allowAnnotation,
+                    mn.visibleAnnotations, mn.invisibleAnnotations,
+                    "method", cn.name, mn.name, mn.desc
+                )
+
+                val an = mn.findAnyAnnotation(visibilityAnnotations) ?: continue
+                val policy = an.policy ?: continue
+                methodPolicies[MethodKey(mn.name, mn.desc)] = policy
+
+                if (policy.policy != FilterPolicy.Substitute) continue
+
+                // Handle substitution
+                val suffix = getAnnotationField(an, "suffix", false) ?: "\$ravenwood"
+                val replacement = mn.name + suffix
+
+                if (replacement == mn.name) {
+                    errors.onErrorFound("@SubstituteWith require a different name")
+                } else {
+                    // The replacement method has to be renamed
+                    methodPolicies[MethodKey(replacement, mn.desc)] =
+                        FilterPolicy.Keep.withReason(REASON_ANNOTATION)
+                    renamedMethods[MethodKey(replacement, mn.desc)] = mn.name
+
+                    log.v("Substitution found: %s%s -> %s", replacement, mn.desc, mn.name)
+                }
+            }
         }
 
         /**
-         * Every time we see a different class, we scan all its methods for substitution attributes,
-         * and compute (implicit) policies caused by them.
+         * Throw if an item has more than one visibility annotations, or the class is not allowed
          *
-         * For example, for the following methods:
-         *
-         *   @Stub
-         *   @Substitute(suffix = "_host")
-         *   private void foo() {
-         *      // This isn't supported on the host side.
-         *   }
-         *   private void foo_host() {
-         *      // Host side implementation
-         *   }
-         *
-         * We internally handle them as:
-         *
-         *   foo() -> Remove
-         *   foo_host() -> Stub, and then rename it to foo().
+         * name1 - 4 are only used in exception messages. We take them as separate strings
+         * to avoid unnecessary string concatenations.
          */
-        private fun setClass(cn: ClassNode) {
-            if (currentClass == cn) {
-                return
-            }
-            // If the class is changing, we'll rebuild the internal structure.
-            currentClass = cn
-
-            policiesFromSubstitution.clear()
-            substituteToMethods.clear()
-
-            for (mn in cn.methods ?: emptyList()) {
-                findAnyAnnotation(substituteAnnotations,
-                        mn.visibleAnnotations,
-                        mn.invisibleAnnotations)?.let { an ->
-
-                    // Find the policy for this method.
-                    val policy = outermostFilter.getPolicyForMethod(cn.name, mn.name, mn.desc)
-                            .policy.resolveClassWidePolicy()
-                    // Make sure it's either Stub or Keep.
-                    if (!(policy.needsInStub || policy.needsInImpl)) {
-                        // TODO: Use the real annotation names in the message
-                        errors.onErrorFound("@SubstituteWith must have either @Stub or @Keep")
-                        return@let
-                    }
-                    if (!policy.isUsableWithMethods) {
-                        throw HostStubGenInternalException("Policy $policy shouldn't show up here")
-                    }
-
-                    val suffix = getAnnotationField(an, "suffix", false) ?: "\$ravenwood"
-                    val renameFrom = mn.name + suffix
-                    val renameTo = mn.name
-
-                    if (renameFrom == renameTo) {
-                        errors.onErrorFound("@SubstituteWith have a different name")
-                        return@let
-                    }
-
-                    // This mn has "SubstituteWith". This means,
-                    // 1. Re move the "rename-to" method, so add it to substitutedMethods.
-                    policiesFromSubstitution[MethodKey(renameTo, mn.desc)] =
-                            FilterPolicy.Remove.withReason("substitute-to")
-
-                    // If the policy is "stub", use "stub".
-                    // Otherwise, it must be "keep" or "throw", but there's no point in using
-                    // "throw", so let's use "keep".
-                    val newPolicy = if (policy.needsInStub) policy else FilterPolicy.Keep
-                    // 2. We also keep the from-to in the map.
-                    policiesFromSubstitution[MethodKey(renameFrom, mn.desc)] =
-                            newPolicy.withReason("substitute-from")
-                    substituteToMethods[MethodKey(renameFrom, mn.desc)] = renameTo
-
-                    log.v("Substitution found: %s%s -> %s", renameFrom, mn.desc, renameTo)
+        private fun detectInvalidAnnotations(
+            className: String,
+            allowAnnotation: Boolean,
+            visibles: List<AnnotationNode>?,
+            invisibles: List<AnnotationNode>?,
+            type: String,
+            name1: String,
+            name2: String = "",
+            name3: String = "",
+        ) {
+            var count = 0
+            var visibleCount = 0
+            for (an in visibles ?: emptyList()) {
+                if (visibilityAnnotations.contains(an.desc)) {
+                    visibleCount++
                 }
+                if (allAnnotations.contains(an.desc)) {
+                    count++
+                }
+            }
+            for (an in invisibles ?: emptyList()) {
+                if (visibilityAnnotations.contains(an.desc)) {
+                    visibleCount++
+                }
+                if (allAnnotations.contains(an.desc)) {
+                    count++
+                }
+            }
+            if (count > 0 && !allowAnnotation) {
+                throw InvalidAnnotationException(
+                    "Class ${className.toHumanReadableClassName()} is not allowed to have " +
+                            "Ravenwood annotations. Contact g/ravenwood for more details."
+                )
+            }
+            if (visibleCount > 1) {
+                val description = if (name2 == "" && name3 == "") {
+                    "$type $name1"
+                } else {
+                    "$type $name1.$name2$name3"
+                }
+                throw InvalidAnnotationException(
+                    "Found more than one visibility annotations on $description"
+                )
             }
         }
     }
@@ -409,8 +302,11 @@
     /**
      * Return the (String) value of 'value' parameter from an annotation.
      */
-    private fun getAnnotationField(an: AnnotationNode, name: String,
-                                   required: Boolean = true): String? {
+    private fun getAnnotationField(
+        an: AnnotationNode,
+        name: String,
+        required: Boolean = true
+    ): String? {
         try {
             val suffix = findAnnotationValueAsString(an, name)
             if (suffix == null && required) {
@@ -424,6 +320,9 @@
     }
 
     companion object {
+        private const val REASON_ANNOTATION = "annotation"
+        private const val REASON_CLASS_ANNOTATION = "class-annotation"
+
         /**
          * Convert from human-readable type names (e.g. "com.android.TypeName") to the internal type
          * names (e.g. "Lcom/android/TypeName).
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
index 47790b1..8ee3a94 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
@@ -16,7 +16,7 @@
 package com.android.hoststubgen.filters
 
 import com.android.hoststubgen.asm.ClassNodes
-import com.android.hoststubgen.asm.getDirectOuterClassName
+import com.android.hoststubgen.asm.isNative
 
 /**
  * This is used as the second last fallback filter. This filter propagates the class-wide policy
@@ -24,74 +24,80 @@
  */
 class ClassWidePolicyPropagatingFilter(
     private val classes: ClassNodes,
-    fallback: OutputFilter,
-    ) : DelegatingFilter(fallback) {
+    fallback: OutputFilter
+) : DelegatingFilter(fallback) {
 
-    private fun getClassWidePolicy(className: String, resolve: Boolean): FilterPolicyWithReason? {
+    /**
+     * We don't use ClassNode.outerClass, because it gives as the top-level
+     * outer class (A$B$C -> A), not the direct outer class (A$B$C -> A$B).
+     *
+     * Sometimes a class name includes `$`, but is not as a nested class name separator
+     * (e.g. a class name like `MyClass$$`). In this case, `MyClass$` is not actually a class.
+     *
+     * So before getting the class policy on a nonexistent class, which may cause an
+     * incorrect result, we make sure the class actually exists.
+     */
+    private fun getDirectOuterClass(className: String): String? {
         var currentClass = className
-
-
-        // If the class name is `a.b.c.A$B$C`, then we try to get the class wide policy
-        // from a.b.c.A$B$C, then a.b.c.A$B, and then a.b.c.A.
         while (true) {
-            // Sometimes a class name has a `$` in it but not as a nest class name separator --
-            // e.g. class name like "MyClass$$". In this case, `MyClass$` may not actually be
-            // a class name.
-            // So before getting the class policy on a nonexistent class, which may cause an
-            // incorrect result, we make sure if className actually exists.
-            if (classes.hasClass(className)) {
-                outermostFilter.getPolicyForClass(className).let { policy ->
-                    if (policy.policy.isClassWidePolicy) {
-                        val p = if (resolve) {
-                            policy.policy.resolveClassWidePolicy()
-                        } else {
-                            policy.policy
-                        }
-
-                        return p.withReason(policy.reason)
-                            .wrapReason("class-wide in $currentClass")
-                    }
-                    // If the class's policy is remove, then remove it.
-                    if (policy.policy == FilterPolicy.Remove) {
-                        return FilterPolicy.Remove.withReason("class-wide in $currentClass")
-                    }
-                }
-            }
-
-            // Next, look at the outer class...
-            val outer = getDirectOuterClassName(currentClass)
-            if (outer == null) {
+            val pos = currentClass.lastIndexOf('$')
+            if (pos < 0) {
                 return null
             }
-            currentClass = outer
+            currentClass = currentClass.substring(0, pos)
+            if (classes.hasClass(currentClass)) {
+                return currentClass
+            }
         }
     }
 
+    private fun getClassWidePolicy(className: String, resolve: Boolean): FilterPolicyWithReason? {
+        outermostFilter.getPolicyForClass(className).let { policy ->
+            if (policy.policy == FilterPolicy.KeepClass) {
+                val p = if (resolve) {
+                    policy.policy.resolveClassWidePolicy()
+                } else {
+                    policy.policy
+                }
+
+                return p.withReason(policy.reason)
+                    .wrapReason("class-wide in $className")
+            }
+            // If the class's policy is remove, then remove it.
+            if (policy.policy == FilterPolicy.Remove) {
+                return FilterPolicy.Remove.withReason("class-wide in $className")
+            }
+        }
+        return null
+    }
+
     override fun getPolicyForClass(className: String): FilterPolicyWithReason {
-        // If it's a nested class, use the outer class's policy.
-        getDirectOuterClassName(className)?.let { outerName ->
-            getClassWidePolicy(outerName, resolve = false)?.let { policy ->
-                return policy
-            }
-        }
-
-        return super.getPolicyForClass(className)
+        // If the class name is `a.b.c.A$B$C`, then we try to get the class wide policy
+        // from a.b.c.A$B$C, then a.b.c.A$B, and then a.b.c.A, recursively
+        return getDirectOuterClass(className)?.let { getClassWidePolicy(it, resolve = false) }
+            ?: super.getPolicyForClass(className)
     }
 
-    override fun getPolicyForField(
-            className: String,
-            fieldName: String
-    ): FilterPolicyWithReason {
+    override fun getPolicyForField(className: String, fieldName: String): FilterPolicyWithReason {
         return getClassWidePolicy(className, resolve = true)
                 ?: super.getPolicyForField(className, fieldName)
     }
 
     override fun getPolicyForMethod(
-            className: String,
-            methodName: String,
-            descriptor: String
+        className: String,
+        methodName: String,
+        descriptor: String
     ): FilterPolicyWithReason {
-        return getClassWidePolicy(className, resolve = true)
-                ?: super.getPolicyForMethod(className, methodName, descriptor)
+        return outermostFilter.getNativeSubstitutionClass(className)?.let {
+            // First check native substitution
+            classes.findMethod(className, methodName, descriptor)?.let { mn ->
+                if (mn.isNative()) {
+                    FilterPolicy.NativeSubstitute.withReason("class-wide in $className")
+                } else {
+                    null
+                }
+            }
+        } ?: getClassWidePolicy(className, resolve = true)
+        ?: super.getPolicyForMethod(className, methodName, descriptor)
     }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
index 678e6ea..be3c59c 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
@@ -26,23 +26,17 @@
  * @param policy the policy. Cannot be a "substitute" policy.
  */
 class ConstantFilter(
-        policy: FilterPolicy,
-        val reason: String
+    policy: FilterPolicy,
+    private val reason: String
 ) : OutputFilter() {
-    val classPolicy: FilterPolicy
-    val fieldPolicy: FilterPolicy
-    val methodPolicy: FilterPolicy
+
+    private val classPolicy: FilterPolicy
+    private val fieldPolicy: FilterPolicy
+    private val methodPolicy: FilterPolicy
 
     init {
-        if (policy.isSubstitute) {
-            throw HostStubGenInternalException(
-                    "ConstantFilter doesn't allow substitution policies.")
-        }
-        if (policy.isClassWidePolicy) {
-            // We prevent it, because there's no point in using class-wide policies because
-            // all members get othe same policy too anyway.
-            throw HostStubGenInternalException(
-                    "ConstantFilter doesn't allow class-wide policies.")
+        if (!policy.isUsableWithDefault) {
+            throw HostStubGenInternalException("ConstantFilter doesn't support $policy.")
         }
         methodPolicy = policy
 
@@ -63,10 +57,10 @@
     }
 
     override fun getPolicyForMethod(
-            className: String,
-            methodName: String,
-            descriptor: String,
-            ): FilterPolicyWithReason {
+        className: String,
+        methodName: String,
+        descriptor: String,
+    ): FilterPolicyWithReason {
         return methodPolicy.withReason(reason)
     }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
index f839444..ab03874 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
@@ -17,37 +17,25 @@
 
 enum class FilterPolicy {
     /**
-     * Keep the item in the stub jar file, so tests can use it.
-     */
-    Stub,
-
-    /**
-     * Keep the item in the impl jar file, but not in the stub file. Tests cannot use it directly,
-     * but indirectly.
+     * Keep the item in the jar file.
      */
     Keep,
 
     /**
-     * Only used for types. Keep the class in the stub, and also all its members.
-     * But each member can have another annotations to override it.
-     */
-    StubClass,
-
-    /**
-     * Only used for types. Keep the class in the impl, not in the stub, and also all its members.
-     * But each member can have another annotations to override it.
+     * Only usable with classes. Keep the class in the jar, and also all its members.
+     * Each member can have another policy to override it.
      */
     KeepClass,
 
     /**
-     * Same as [Stub], but replace it with a "substitution" method. Only usable with methods.
+     * Only usable with methods. Replace a method with a "substitution" method.
      */
-    SubstituteAndStub,
+    Substitute,
 
     /**
-     * Same as [Keep], but replace it with a "substitution" method. Only usable with methods.
+     * Only usable with methods. Replace a native method with a "substitution" method,
      */
-    SubstituteAndKeep,
+    NativeSubstitute,
 
     /**
      * Only usable with methods. The item will be kept in the impl jar file, but when called,
@@ -57,7 +45,7 @@
 
     /**
      * Only usable with methods. The item will be kept in the impl jar file, but when called,
-     * it'll no-op.  Currently only supported for methods returning `void`.
+     * it'll no-op.
      */
     Ignore,
 
@@ -66,20 +54,19 @@
      */
     Remove;
 
-    val isSubstitute: Boolean
-        get() = this == SubstituteAndStub || this == SubstituteAndKeep
-
-    val needsInStub: Boolean
-        get() = this == Stub || this == StubClass || this == SubstituteAndStub || this == Ignore
-
-    val needsInImpl: Boolean
-        get() = this != Remove
+    val needsInOutput: Boolean
+        get() {
+            return when (this) {
+                Remove -> false
+                else -> true
+            }
+        }
 
     /** Returns whether a policy can be used with classes */
     val isUsableWithClasses: Boolean
         get() {
             return when (this) {
-                Stub, StubClass, Keep, KeepClass, Remove -> true
+                Keep, KeepClass, Remove -> true
                 else -> false
             }
         }
@@ -88,7 +75,7 @@
     val isUsableWithFields: Boolean
         get() {
             return when (this) {
-                Stub, Keep, Remove -> true
+                Keep, Remove -> true
                 else -> false
             }
         }
@@ -97,16 +84,16 @@
     val isUsableWithMethods: Boolean
         get() {
             return when (this) {
-                StubClass, KeepClass -> false
+                KeepClass -> false
                 else -> true
             }
         }
 
-    /** Returns whether a policy is a class-wide one. */
-    val isClassWidePolicy: Boolean
+    /** Returns whether a policy can be used as default policy. */
+    val isUsableWithDefault: Boolean
         get() {
             return when (this) {
-                StubClass, KeepClass -> true
+                Keep, Throw, Remove -> true
                 else -> false
             }
         }
@@ -116,25 +103,24 @@
         get() {
             return when (this) {
                 // TODO: handle native method with no substitution as being unsupported
-                Stub, StubClass, Keep, KeepClass, SubstituteAndStub, SubstituteAndKeep -> true
+                Keep, KeepClass, Substitute, NativeSubstitute -> true
                 else -> false
             }
         }
 
-    fun getSubstitutionBasePolicy(): FilterPolicy {
-        return when (this) {
-            SubstituteAndKeep -> Keep
-            SubstituteAndStub -> Stub
-            else -> this
+    val isMethodRewriteBody: Boolean
+        get() {
+            return when (this) {
+                NativeSubstitute, Throw, Ignore -> true
+                else -> false
+            }
         }
-    }
 
     /**
-     * Convert {Stub,Keep}Class to the corresponding Stub or Keep.
+     * Convert KeepClass to Keep, or return itself.
      */
     fun resolveClassWidePolicy(): FilterPolicy {
         return when (this) {
-            StubClass -> Stub
             KeepClass -> Keep
             else -> this
         }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
index eb03f66..b10165b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
@@ -30,36 +30,6 @@
         return FilterPolicyWithReason(policy, "$reason [inner-reason: ${this.reason}]")
     }
 
-    /**
-     * If the visibility is lower than "Keep" (meaning if it's "remove"),
-     * then return a new [FilterPolicy] with "Keep".
-     * Otherwise, return itself
-     */
-    fun promoteToKeep(promotionReason: String): FilterPolicyWithReason {
-        if (policy.needsInImpl) {
-            return this
-        }
-        val newPolicy = if (policy.isClassWidePolicy) FilterPolicy.KeepClass else FilterPolicy.Keep
-
-        return FilterPolicyWithReason(newPolicy,
-                "$promotionReason [original remove reason: ${this.reason}]")
-    }
-
-    /**
-     * If the visibility is above "Keep" (meaning if it's "stub"),
-     * then return a new [FilterPolicy] with "Keep".
-     * Otherwise, return itself
-     */
-    fun demoteToKeep(promotionReason: String): FilterPolicyWithReason {
-        if (!policy.needsInStub) {
-            return this
-        }
-        val newPolicy = if (policy.isClassWidePolicy) FilterPolicy.KeepClass else FilterPolicy.Keep
-
-        return FilterPolicyWithReason(newPolicy,
-                "$promotionReason [original stub reason: ${this.reason}]")
-    }
-
     override fun toString(): String {
         return "[$policy - reason: $reason]"
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index 5a26fc6..474da6d 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -25,7 +25,6 @@
 import com.android.hoststubgen.asm.isAutoGeneratedEnumMember
 import com.android.hoststubgen.asm.isEnum
 import com.android.hoststubgen.asm.isSynthetic
-import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
 import com.android.hoststubgen.log
 import org.objectweb.asm.tree.ClassNode
 
@@ -53,7 +52,7 @@
             }
             // If the outer class needs to be in impl, it should be in impl too.
             val outerPolicy = outermostFilter.getPolicyForClass(cn.outerClass)
-            if (outerPolicy.policy.needsInImpl) {
+            if (outerPolicy.policy.needsInOutput) {
                 return FilterPolicy.KeepClass.withReason("anonymous-inner-class")
             }
         }
@@ -79,19 +78,6 @@
         val fallback = super.getPolicyForMethod(className, methodName, descriptor)
         val classPolicy = outermostFilter.getPolicyForClass(className)
 
-        // If the class is in the stub, then we need to put the private constructor in the stub too,
-        // to prevent the class from getting instantiated.
-        if (classPolicy.policy.needsInStub &&
-                !fallback.policy.needsInStub &&
-                (methodName == "<init>") && // Constructor?
-                (descriptor == "()V")) { // Has zero parameters?
-            classes.findMethod(className, methodName, descriptor)?.let { mn ->
-                if (isVisibilityPrivateOrPackagePrivate(mn.access)) {
-                    return FilterPolicy.Stub.withReason("private constructor in stub class")
-                }
-            }
-        }
-
         val cn = classes.getClass(className)
 
         // If we throw from the static initializer, the class would be useless, so we convert it
@@ -107,7 +93,7 @@
         }
 
         log.d("Class ${cn.name} Class policy: $classPolicy")
-        if (classPolicy.policy.needsInImpl) {
+        if (classPolicy.policy.needsInOutput) {
             // Do it only when the class needs to be kept...
 
             // Member policy should be "keep" or "stub".
@@ -152,7 +138,7 @@
         val classPolicy = outermostFilter.getPolicyForClass(className)
 
         log.d("Class ${cn.name} Class policy: $classPolicy")
-        if (classPolicy.policy.needsInImpl) {
+        if (classPolicy.policy.needsInOutput) {
             // Do it only when the class needs to be kept...
 
             // Member policy should be "keep" or "stub".
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/NativeFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/NativeFilter.kt
new file mode 100644
index 0000000..bd71931
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/NativeFilter.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.isNative
+
+class NativeFilter(
+    private val classes: ClassNodes,
+    fallback: OutputFilter
+) : DelegatingFilter(fallback) {
+    override fun getPolicyForMethod(
+        className: String,
+        methodName: String,
+        descriptor: String,
+    ): FilterPolicyWithReason {
+        return classes.findMethod(className, methodName, descriptor)?.let { mn ->
+            // For native methods that weren't handled by outer filters,
+            // we keep it so that native method registration will not crash.
+            if (mn.isNative()) {
+                FilterPolicy.Keep.withReason("native-preserve")
+            } else {
+                null
+            }
+        } ?: super.getPolicyForMethod(className, methodName, descriptor)
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/StubIntersectingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/StubIntersectingFilter.kt
deleted file mode 100644
index f92a027..0000000
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/StubIntersectingFilter.kt
+++ /dev/null
@@ -1,91 +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.hoststubgen.filters
-
-import com.android.hoststubgen.HostStubGenErrors
-import com.android.hoststubgen.asm.ClassNodes
-
-private const val REASON = "demoted, not in intersect jars"
-
-/**
- * An [OutputFilter] that will restrict what to put in stub to only what shows up in "intersecting
- * jar" files.
- *
- * For example, if the Android public API stub jar is provided, then the HostStubGen's output
- * stub will be restricted to public APIs.
- */
-class StubIntersectingFilter(
-        private val errors: HostStubGenErrors,
-        /**
-         * If a class / field / method is not in any of these jars, then we will not put it in
-         * stub.
-         */
-        private val intersectingJars: Map<String, ClassNodes>,
-        fallback: OutputFilter,
-) : DelegatingFilter(fallback) {
-    private inline fun exists(predicate: (ClassNodes) -> Boolean): Boolean {
-        intersectingJars.forEach { entry ->
-            if (predicate(entry.value)) {
-                return true
-            }
-        }
-        return false
-    }
-
-    /**
-     * If [origPolicy] is less than "Stub", then return it as-is.
-     *
-     * Otherwise, call [inStubChecker] to see if the API is in any of [intersectingJars].
-     * If yes, then return [origPolicy] as-is. Otherwise, demote to "Keep".
-     */
-    private fun intersectWithStub(
-            origPolicy: FilterPolicyWithReason,
-            inStubChecker: () -> Boolean,
-    ): FilterPolicyWithReason {
-        if (origPolicy.policy.needsInStub) {
-            // Only check the stub jars, when the class is supposed to be in stub otherwise.
-            if (!inStubChecker()) {
-                return origPolicy.demoteToKeep(REASON)
-            }
-        }
-        return origPolicy
-    }
-
-    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
-        return intersectWithStub(super.getPolicyForClass(className)) {
-            exists { classes -> classes.findClass(className) != null }
-        }
-    }
-
-    override fun getPolicyForField(
-            className: String,
-            fieldName: String
-    ): FilterPolicyWithReason {
-        return intersectWithStub(super.getPolicyForField(className, fieldName)) {
-            exists { classes -> classes.findField(className, fieldName) != null }
-        }
-    }
-
-    override fun getPolicyForMethod(
-            className: String,
-            methodName: String,
-            descriptor: String
-    ): FilterPolicyWithReason {
-        return intersectWithStub(super.getPolicyForMethod(className, methodName, descriptor)) {
-            exists { classes -> classes.findMethod(className, methodName, descriptor) != null }
-        }
-    }
-}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 53bcf10..14fd82b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -240,7 +240,7 @@
 
                             imf.setPolicyForMethod(className, name, signature,
                                     policy.withReason(FILTER_REASON))
-                            if (policy.isSubstitute) {
+                            if (policy == FilterPolicy.Substitute) {
                                 val fromName = fields[3].substring(1)
 
                                 if (fromName == name) {
@@ -248,10 +248,9 @@
                                             "Substitution must have a different name")
                                 }
 
-                                // Set the policy  for the "from" method.
+                                // Set the policy for the "from" method.
                                 imf.setPolicyForMethod(className, fromName, signature,
-                                        policy.getSubstitutionBasePolicy()
-                                                .withReason(FILTER_REASON))
+                                    FilterPolicy.Keep.withReason(FILTER_REASON))
 
                                 val classAndMethod = splitWithLastPeriod(fromName)
                                 if (classAndMethod != null) {
@@ -346,18 +345,14 @@
 
 private fun parsePolicy(s: String): FilterPolicy {
     return when (s.lowercase()) {
-        "s", "stub" -> FilterPolicy.Stub
         "k", "keep" -> FilterPolicy.Keep
         "t", "throw" -> FilterPolicy.Throw
         "r", "remove" -> FilterPolicy.Remove
-        "sc", "stubclass" -> FilterPolicy.StubClass
         "kc", "keepclass" -> FilterPolicy.KeepClass
         "i", "ignore" -> FilterPolicy.Ignore
         else -> {
             if (s.startsWith("@")) {
-                FilterPolicy.SubstituteAndStub
-            } else if (s.startsWith("%")) {
-                FilterPolicy.SubstituteAndKeep
+                FilterPolicy.Substitute
             } else {
                 throw ParseException("Invalid policy \"$s\"")
             }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt
index 01a7ab3..7440b94 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt
@@ -24,19 +24,19 @@
 /**
  * General purpose filter for class names.
  */
-class ClassFilter private constructor (
-        val defaultResult: Boolean,
+class ClassFilter private constructor(
+    private val defaultResult: Boolean,
 ) {
-    private data class FilterElement(
-            val allowed: Boolean,
-            val internalName: String,
-            val isPrefix: Boolean,
+    private class FilterElement(
+        val allowed: Boolean,
+        val internalName: String,
+        val isPrefix: Boolean,
     ) {
         fun matches(classInternalName: String): Boolean {
-            if (isPrefix) {
-                return classInternalName.startsWith(internalName)
+            return if (isPrefix) {
+                classInternalName.startsWith(internalName)
             } else {
-                return classInternalName == internalName
+                classInternalName == internalName
             }
         }
     }
@@ -54,15 +54,16 @@
             return it
         }
 
-        var result = defaultResult
-        run outer@{
-            elements.forEach { e ->
-                if (e.matches(classInternalName)) {
-                    result = e.allowed
-                    return@outer // break equivalent.
-                }
+        val testClasses = sequence {
+            // Yield itself and its outer class(es) one by one
+            var idx = classInternalName.length
+            while (idx > 0) {
+                yield(classInternalName.substring(0, idx))
+                idx = classInternalName.lastIndexOf('$', idx - 1)
             }
         }
+
+        val result = elements.find { testClasses.any(it::matches) }?.allowed ?: defaultResult
         cache[classInternalName] = result
 
         return result
@@ -87,9 +88,9 @@
 
         /** Build a filter from a string (for unit tests). */
         fun buildFromString(
-                filterString: String,
-                defaultResult: Boolean,
-                filenameForErrorMessage: String
+            filterString: String,
+            defaultResult: Boolean,
+            filenameForErrorMessage: String
         ): ClassFilter {
             val ret = ClassFilter(defaultResult)
 
@@ -119,17 +120,20 @@
 
                 // Handle wildcard -- e.g. "package.name.*"
                 if (line.endsWith(".*")) {
-                    ret.elements.add(FilterElement(
-                            allow, line.substring(0, line.length - 2).toJvmClassName(), true))
+                    ret.elements.add(
+                        FilterElement(
+                            allow, line.substring(0, line.length - 2).toJvmClassName(), true
+                        )
+                    )
                     return@forEach
                 }
 
                 // Any other uses of "*" would be an error.
                 if (line.contains('*')) {
                     throw ParseException(
-                            "Wildcard (*) can only show up as the last element",
-                            filenameForErrorMessage,
-                            lineNo
+                        "Wildcard (*) can only show up as the last element",
+                        filenameForErrorMessage,
+                        lineNo
                     )
                 }
                 ret.elements.add(FilterElement(allow, line.toJvmClassName(), false))
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index bad0449..41ba928 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -26,67 +26,47 @@
 import com.android.hoststubgen.filters.FilterPolicy
 import com.android.hoststubgen.filters.FilterPolicyWithReason
 import com.android.hoststubgen.filters.OutputFilter
-import com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-import com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+import com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 import com.android.hoststubgen.log
+import java.io.PrintWriter
 import org.objectweb.asm.ClassVisitor
 import org.objectweb.asm.FieldVisitor
 import org.objectweb.asm.MethodVisitor
 import org.objectweb.asm.Opcodes
 import org.objectweb.asm.commons.ClassRemapper
-import org.objectweb.asm.commons.Remapper
 import org.objectweb.asm.util.TraceClassVisitor
-import java.io.PrintWriter
 
-val OPCODE_VERSION = Opcodes.ASM9
+const val OPCODE_VERSION = Opcodes.ASM9
 
-abstract class BaseAdapter (
-        protected val classes: ClassNodes,
-        nextVisitor: ClassVisitor,
-        protected val filter: OutputFilter,
-        protected val options: Options,
+abstract class BaseAdapter(
+    protected val classes: ClassNodes,
+    nextVisitor: ClassVisitor,
+    protected val filter: OutputFilter,
+    protected val options: Options,
 ) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
 
     /**
      * Options to control the behavior.
      */
-    data class Options (
-            val errors: HostStubGenErrors,
-            val stats: HostStubGenStats?,
-            val enablePreTrace: Boolean,
-            val enablePostTrace: Boolean,
-            val enableNonStubMethodCallDetection: Boolean,
-            )
+    data class Options(
+        val errors: HostStubGenErrors,
+        val stats: HostStubGenStats?,
+        val enablePreTrace: Boolean,
+        val enablePostTrace: Boolean
+    )
 
     protected lateinit var currentPackageName: String
     protected lateinit var currentClassName: String
     protected var nativeSubstitutionClass: String? = null
     protected lateinit var classPolicy: FilterPolicyWithReason
 
-    /**
-     * Return whether an item with a given policy should be included in the output.
-     */
-    protected abstract fun shouldEmit(policy: FilterPolicy): Boolean
-
-    /**
-     * Inject [HostStubGenKeptInStub] and [HostStubGenKeptInImpl] as needed to an item.
-     */
-    protected fun injectInStubAndKeepAnnotations(policy: FilterPolicy, v: UnifiedVisitor) {
-        if (policy.needsInStub) {
-            v.visitAnnotation(HostStubGenKeptInStub.CLASS_DESCRIPTOR, true)
-        }
-        if (policy.needsInImpl) {
-            v.visitAnnotation(HostStubGenKeptInImpl.CLASS_DESCRIPTOR, true)
-        }
-    }
-
     override fun visit(
-            version: Int,
-            access: Int,
-            name: String,
-            signature: String?,
-            superName: String?,
-            interfaces: Array<String>,
+        version: Int,
+        access: Int,
+        name: String,
+        signature: String?,
+        superName: String?,
+        interfaces: Array<String>,
     ) {
         super.visit(version, access, name, signature, superName, interfaces)
         currentClassName = name
@@ -103,21 +83,25 @@
                 .toJvmClassName()
             log.d("  NativeSubstitutionClass: $fullClassName")
             if (classes.findClass(fullClassName) == null) {
-                log.w("Native substitution class $fullClassName not found. Class must be " +
-                        "available at runtime.")
+                log.w(
+                    "Native substitution class $fullClassName not found. Class must be " +
+                            "available at runtime."
+                )
             } else {
                 // If the class exists, it must have a KeepClass policy.
                 if (filter.getPolicyForClass(fullClassName).policy != FilterPolicy.KeepClass) {
                     // TODO: Use real annotation name.
                     options.errors.onErrorFound(
-                            "Native substitution class $fullClassName should have @Keep.")
+                        "Native substitution class $fullClassName should have @Keep."
+                    )
                 }
             }
 
             nativeSubstitutionClass = fullClassName
         }
+
         // Inject annotations to generated classes.
-        injectInStubAndKeepAnnotations(classPolicy.policy, UnifiedVisitor.on(this))
+        UnifiedVisitor.on(this).visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true)
     }
 
     override fun visitEnd() {
@@ -141,11 +125,11 @@
     }
 
     override fun visitField(
-            access: Int,
-            name: String,
-            descriptor: String,
-            signature: String?,
-            value: Any?,
+        access: Int,
+        name: String,
+        descriptor: String,
+        signature: String?,
+        value: Any?,
     ): FieldVisitor? {
         if (skipMemberModificationNestCount > 0) {
             return super.visitField(access, name, descriptor, signature, value)
@@ -154,7 +138,7 @@
         log.d("visitField: %s %s [%x] Policy: %s", name, descriptor, access, policy)
 
         log.withIndent {
-            if (!shouldEmit(policy.policy)) {
+            if (policy.policy == FilterPolicy.Remove) {
                 log.d("Removing %s %s", name, policy)
                 return null
             }
@@ -162,18 +146,19 @@
             log.v("Emitting field: %s %s %s", name, descriptor, policy)
             val ret = super.visitField(access, name, descriptor, signature, value)
 
-            injectInStubAndKeepAnnotations(policy.policy, UnifiedVisitor.on(ret))
+            UnifiedVisitor.on(ret)
+                .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true)
 
             return ret
         }
     }
 
     override fun visitMethod(
-            access: Int,
-            name: String,
-            descriptor: String,
-            signature: String?,
-            exceptions: Array<String>?,
+        access: Int,
+        name: String,
+        descriptor: String,
+        signature: String?,
+        exceptions: Array<String>?,
     ): MethodVisitor? {
         if (skipMemberModificationNestCount > 0) {
             return super.visitMethod(access, name, descriptor, signature, exceptions)
@@ -187,11 +172,11 @@
             // Instead of this method, we rename the substitute-to method with the original
             // name, in the "Maybe rename the method" part below.
             val policy = filter.getPolicyForMethod(currentClassName, name, descriptor)
-            if (policy.policy.isSubstitute) {
+            if (policy.policy == FilterPolicy.Substitute) {
                 log.d("Skipping %s%s %s", name, descriptor, policy)
                 return null
             }
-            if (!shouldEmit(p.policy)) {
+            if (p.policy == FilterPolicy.Remove) {
                 log.d("Removing %s%s %s", name, descriptor, policy)
                 return null
             }
@@ -209,13 +194,16 @@
                 // `name` is the name of the method we're currently visiting, so it's usually a
                 // "...$ravewnwood" name.
                 newAccess = checkSubstitutionMethodCompatibility(
-                        classes, currentClassName, newName, name, descriptor, options.errors)
+                    classes, currentClassName, newName, name, descriptor, options.errors
+                )
                 if (newAccess == NOT_COMPATIBLE) {
                     return null
                 }
 
-                log.v("Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
-                        newName, policy)
+                log.v(
+                    "Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
+                    newName, policy
+                )
             } else {
                 log.v("Emitting method: %s%s %s", name, descriptor, policy)
                 newName = name
@@ -225,14 +213,17 @@
             // But note, we only use it when calling the super's method,
             // but not for visitMethodInner(), because when subclass wants to change access,
             // it can do so inside visitMethodInner().
-            newAccess = updateAccessFlags(newAccess, name, descriptor)
+            newAccess = updateAccessFlags(newAccess, name, descriptor, policy.policy)
 
-            val ret = visitMethodInner(access, newName, descriptor, signature, exceptions, policy,
+            val ret = visitMethodInner(
+                access, newName, descriptor, signature, exceptions, policy,
                 renameTo != null,
-                super.visitMethod(newAccess, newName, descriptor, signature, exceptions))
+                super.visitMethod(newAccess, newName, descriptor, signature, exceptions)
+            )
 
             ret?.let {
-                injectInStubAndKeepAnnotations(policy.policy, UnifiedVisitor.on(ret))
+                UnifiedVisitor.on(ret)
+                    .visitAnnotation(HostStubGenProcessedAsKeep.CLASS_DESCRIPTOR, true)
             }
 
             return ret
@@ -240,9 +231,10 @@
     }
 
     open fun updateAccessFlags(
-            access: Int,
-            name: String,
-            descriptor: String,
+        access: Int,
+        name: String,
+        descriptor: String,
+        policy: FilterPolicy,
     ): Int {
         return access
     }
@@ -256,7 +248,7 @@
         policy: FilterPolicyWithReason,
         substituted: Boolean,
         superVisitor: MethodVisitor?,
-        ): MethodVisitor?
+    ): MethodVisitor?
 
     companion object {
         fun getVisitor(
@@ -265,8 +257,6 @@
             nextVisitor: ClassVisitor,
             filter: OutputFilter,
             packageRedirector: PackageRedirectRemapper,
-            remapper: Remapper?,
-            forImpl: Boolean,
             options: Options,
         ): ClassVisitor {
             var next = nextVisitor
@@ -289,23 +279,20 @@
                 if (!packageRedirector.isTarget(classInternalName)) {
                     next = ClassRemapper(next, packageRedirector)
                 } else {
-                    log.v("Class $classInternalName is a redirect-from class, not applying" +
-                            " --package-redirect")
+                    log.v(
+                        "Class $classInternalName is a redirect-from class, not applying" +
+                                " --package-redirect"
+                    )
                 }
             }
 
-            var ret: ClassVisitor
-            if (forImpl) {
-                ret = ImplGeneratingAdapter(classes, next, filter, options)
-            } else {
-                ret = StubGeneratingAdapter(classes, next, filter, options)
-            }
+            next = ImplGeneratingAdapter(classes, next, filter, options)
 
             // Inject TraceClassVisitor for debugging.
             if (options.enablePreTrace) {
-                ret = TraceClassVisitor(ret, verbosePrinter)
+                next = TraceClassVisitor(next, verbosePrinter)
             }
-            return ret
+            return next
         }
     }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt
index 8250412..55d0c0e 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt
@@ -20,38 +20,23 @@
 import org.objectweb.asm.Handle
 import org.objectweb.asm.Label
 import org.objectweb.asm.MethodVisitor
-import org.objectweb.asm.Opcodes
 import org.objectweb.asm.TypePath
 
 /**
- * A method visitor that removes everything from method body.
+ * A method visitor that creates or replaces a method body.
  *
- * To inject a method body, override [visitCode] and create the opcodes there.
+ * Override [emitNewCode] to build the method body.
  */
 abstract class BodyReplacingMethodVisitor(
-    access: Int,
-    name: String,
-    descriptor: String,
-    signature: String?,
-    exceptions: Array<String>?,
-    next: MethodVisitor?,
+    private val createBody: Boolean,
+    next: MethodVisitor?
 ) : MethodVisitor(OPCODE_VERSION, next) {
-    val isVoid: Boolean
-    val isStatic: Boolean
-
-    init {
-        isVoid = descriptor.endsWith(")V")
-        isStatic = access and Opcodes.ACC_STATIC != 0
-    }
 
     // Following methods are for things that we need to keep.
     // Since they're all calling the super method, we can just remove them, but we keep them
     // just to clarify what we're keeping.
 
-    final override fun visitParameter(
-            name: String?,
-            access: Int
-    ) {
+    final override fun visitParameter(name: String?, access: Int) {
         super.visitParameter(name, access)
     }
 
@@ -59,10 +44,7 @@
         return super.visitAnnotationDefault()
     }
 
-    final override fun visitAnnotation(
-            descriptor: String?,
-            visible: Boolean
-    ): AnnotationVisitor? {
+    final override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
         return super.visitAnnotation(descriptor, visible)
     }
 
@@ -75,17 +57,14 @@
         return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)
     }
 
-    final override fun visitAnnotableParameterCount(
-            parameterCount: Int,
-            visible: Boolean
-    ) {
+    final override fun visitAnnotableParameterCount(parameterCount: Int, visible: Boolean) {
         super.visitAnnotableParameterCount(parameterCount, visible)
     }
 
     final override fun visitParameterAnnotation(
-            parameter: Int,
-            descriptor: String?,
-            visible: Boolean
+        parameter: Int,
+        descriptor: String?,
+        visible: Boolean
     ): AnnotationVisitor? {
         return super.visitParameterAnnotation(parameter, descriptor, visible)
     }
@@ -94,10 +73,6 @@
         super.visitAttribute(attribute)
     }
 
-    override fun visitEnd() {
-        super.visitEnd()
-    }
-
     /**
      * Control when to emit the code. We use this to ignore all visitXxx method calls caused by
      * the original method, so we'll remove all the original code.
@@ -108,9 +83,18 @@
      * (See also https://asm.ow2.io/asm4-guide.pdf section 3.2.1 about the MethovVisitor
      * call order.)
      */
-    var emitCode = false
+    private var emitCode = false
+
+    /**
+     * This value will be set as true when [visitCode] is called. In [visitEnd], if this value
+     * is still false, this means that the original method does not have a body.
+     *
+     * We want to forcefully inject a method body in [visitEnd] if [createBody] is true.
+     */
+    private var visitedCode = false
 
     final override fun visitCode() {
+        visitedCode = true
         super.visitCode()
 
         try {
@@ -122,15 +106,19 @@
         }
     }
 
+    final override fun visitEnd() {
+        if (!visitedCode && createBody) {
+            visitCode()
+        }
+        super.visitEnd()
+    }
+
     /**
      * Subclass must implement it and emit code, and call [visitMaxs] at the end.
      */
     abstract fun emitNewCode()
 
-    final override fun visitMaxs(
-            maxStack: Int,
-            maxLocals: Int
-    ) {
+    final override fun visitMaxs(maxStack: Int, maxLocals: Int) {
         if (emitCode) {
             super.visitMaxs(maxStack, maxLocals)
         }
@@ -140,11 +128,11 @@
     // emit any of them, so they are all no-op.
 
     final override fun visitFrame(
-            type: Int,
-            numLocal: Int,
-            local: Array<out Any>?,
-            numStack: Int,
-            stack: Array<out Any>?
+        type: Int,
+        numLocal: Int,
+        local: Array<out Any>?,
+        numStack: Int,
+        stack: Array<out Any>?
     ) {
         if (emitCode) {
             super.visitFrame(type, numLocal, local, numStack, stack)
@@ -157,38 +145,29 @@
         }
     }
 
-    final override fun visitIntInsn(
-            opcode: Int,
-            operand: Int
-    ) {
+    final override fun visitIntInsn(opcode: Int, operand: Int) {
         if (emitCode) {
             super.visitIntInsn(opcode, operand)
         }
     }
 
-    final override fun visitVarInsn(
-            opcode: Int,
-            varIndex: Int
-    ) {
+    final override fun visitVarInsn(opcode: Int, varIndex: Int) {
         if (emitCode) {
             super.visitVarInsn(opcode, varIndex)
         }
     }
 
-    final override fun visitTypeInsn(
-            opcode: Int,
-            type: String?
-    ) {
+    final override fun visitTypeInsn(opcode: Int, type: String?) {
         if (emitCode) {
             super.visitTypeInsn(opcode, type)
         }
     }
 
     final override fun visitFieldInsn(
-            opcode: Int,
-            owner: String?,
-            name: String?,
-            descriptor: String?
+        opcode: Int,
+        owner: String?,
+        name: String?,
+        descriptor: String?
     ) {
         if (emitCode) {
             super.visitFieldInsn(opcode, owner, name, descriptor)
@@ -196,11 +175,11 @@
     }
 
     final override fun visitMethodInsn(
-            opcode: Int,
-            owner: String?,
-            name: String?,
-            descriptor: String?,
-            isInterface: Boolean
+        opcode: Int,
+        owner: String?,
+        name: String?,
+        descriptor: String?,
+        isInterface: Boolean
     ) {
         if (emitCode) {
             super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
@@ -208,21 +187,20 @@
     }
 
     final override fun visitInvokeDynamicInsn(
-            name: String?,
-            descriptor: String?,
-            bootstrapMethodHandle: Handle?,
-            vararg bootstrapMethodArguments: Any?
+        name: String?,
+        descriptor: String?,
+        bootstrapMethodHandle: Handle?,
+        vararg bootstrapMethodArguments: Any?
     ) {
         if (emitCode) {
-            super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle,
-                    *bootstrapMethodArguments)
+            super.visitInvokeDynamicInsn(
+                name, descriptor, bootstrapMethodHandle,
+                *bootstrapMethodArguments
+            )
         }
     }
 
-    final override fun visitJumpInsn(
-            opcode: Int,
-            label: Label?
-    ) {
+    final override fun visitJumpInsn(opcode: Int, label: Label?) {
         if (emitCode) {
             super.visitJumpInsn(opcode, label)
         }
@@ -240,20 +218,17 @@
         }
     }
 
-    final override fun visitIincInsn(
-            varIndex: Int,
-            increment: Int
-    ) {
+    final override fun visitIincInsn(varIndex: Int, increment: Int) {
         if (emitCode) {
             super.visitIincInsn(varIndex, increment)
         }
     }
 
     final override fun visitTableSwitchInsn(
-            min: Int,
-            max: Int,
-            dflt: Label?,
-            vararg labels: Label?
+        min: Int,
+        max: Int,
+        dflt: Label?,
+        vararg labels: Label?
     ) {
         if (emitCode) {
             super.visitTableSwitchInsn(min, max, dflt, *labels)
@@ -261,29 +236,26 @@
     }
 
     final override fun visitLookupSwitchInsn(
-            dflt: Label?,
-            keys: IntArray?,
-            labels: Array<out Label>?
+        dflt: Label?,
+        keys: IntArray?,
+        labels: Array<out Label>?
     ) {
         if (emitCode) {
             super.visitLookupSwitchInsn(dflt, keys, labels)
         }
     }
 
-    final override fun visitMultiANewArrayInsn(
-            descriptor: String?,
-            numDimensions: Int
-    ) {
+    final override fun visitMultiANewArrayInsn(descriptor: String?, numDimensions: Int) {
         if (emitCode) {
             super.visitMultiANewArrayInsn(descriptor, numDimensions)
         }
     }
 
     final override fun visitInsnAnnotation(
-            typeRef: Int,
-            typePath: TypePath?,
-            descriptor: String?,
-            visible: Boolean
+        typeRef: Int,
+        typePath: TypePath?,
+        descriptor: String?,
+        visible: Boolean
     ): AnnotationVisitor? {
         if (emitCode) {
             return super.visitInsnAnnotation(typeRef, typePath, descriptor, visible)
@@ -292,10 +264,10 @@
     }
 
     final override fun visitTryCatchBlock(
-            start: Label?,
-            end: Label?,
-            handler: Label?,
-            type: String?
+        start: Label?,
+        end: Label?,
+        handler: Label?,
+        type: String?
     ) {
         if (emitCode) {
             super.visitTryCatchBlock(start, end, handler, type)
@@ -303,10 +275,10 @@
     }
 
     final override fun visitTryCatchAnnotation(
-            typeRef: Int,
-            typePath: TypePath?,
-            descriptor: String?,
-            visible: Boolean
+        typeRef: Int,
+        typePath: TypePath?,
+        descriptor: String?,
+        visible: Boolean
     ): AnnotationVisitor? {
         if (emitCode) {
             return super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible)
@@ -315,12 +287,12 @@
     }
 
     final override fun visitLocalVariable(
-            name: String?,
-            descriptor: String?,
-            signature: String?,
-            start: Label?,
-            end: Label?,
-            index: Int
+        name: String?,
+        descriptor: String?,
+        signature: String?,
+        start: Label?,
+        end: Label?,
+        index: Int
     ) {
         if (emitCode) {
             super.visitLocalVariable(name, descriptor, signature, start, end, index)
@@ -328,25 +300,23 @@
     }
 
     final override fun visitLocalVariableAnnotation(
-            typeRef: Int,
-            typePath: TypePath?,
-            start: Array<out Label>?,
-            end: Array<out Label>?,
-            index: IntArray?,
-            descriptor: String?,
-            visible: Boolean
+        typeRef: Int,
+        typePath: TypePath?,
+        start: Array<out Label>?,
+        end: Array<out Label>?,
+        index: IntArray?,
+        descriptor: String?,
+        visible: Boolean
     ): AnnotationVisitor? {
         if (emitCode) {
             return super.visitLocalVariableAnnotation(
-                    typeRef, typePath, start, end, index, descriptor, visible)
+                typeRef, typePath, start, end, index, descriptor, visible
+            )
         }
         return null
     }
 
-    final override fun visitLineNumber(
-            line: Int,
-            start: Label?
-    ) {
+    final override fun visitLineNumber(line: Int, start: Label?) {
         if (emitCode) {
             super.visitLineNumber(line, start)
         }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index 3d2e142..057d653 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -18,7 +18,6 @@
 import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
 import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
 import com.android.hoststubgen.asm.ClassNodes
-import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
 import com.android.hoststubgen.asm.prependArgTypeToMethodDescriptor
 import com.android.hoststubgen.asm.writeByteCodeToPushArguments
 import com.android.hoststubgen.asm.writeByteCodeToReturn
@@ -42,16 +41,12 @@
  * An adapter that generates the "impl" class file from an input class file.
  */
 class ImplGeneratingAdapter(
-        classes: ClassNodes,
-        nextVisitor: ClassVisitor,
-        filter: OutputFilter,
-        options: Options,
+    classes: ClassNodes,
+    nextVisitor: ClassVisitor,
+    filter: OutputFilter,
+    options: Options,
 ) : BaseAdapter(classes, nextVisitor, filter, options) {
 
-    override fun shouldEmit(policy: FilterPolicy): Boolean {
-        return policy.needsInImpl
-    }
-
     private var classLoadHooks: List<String> = emptyList()
 
     override fun visit(
@@ -107,14 +102,14 @@
     private fun writeClassLoadHookCalls(mv: MethodVisitor) {
         classLoadHooks.forEach { classLoadHook ->
             // First argument: the class type.
-            mv.visitLdcInsn(Type.getType("L" + currentClassName + ";"))
+            mv.visitLdcInsn(Type.getType("L$currentClassName;"))
 
             // Second argument: method name
             mv.visitLdcInsn(classLoadHook)
 
             // Call HostTestUtils.onClassLoaded().
             mv.visitMethodInsn(
-                Opcodes.INVOKESTATIC,
+                INVOKESTATIC,
                 HostTestUtils.CLASS_INTERNAL_NAME,
                 "onClassLoaded",
                 "(Ljava/lang/Class;Ljava/lang/String;)V",
@@ -124,69 +119,49 @@
     }
 
     override fun updateAccessFlags(
-            access: Int,
-            name: String,
-            descriptor: String,
+        access: Int,
+        name: String,
+        descriptor: String,
+        policy: FilterPolicy,
     ): Int {
-        if ((access and Opcodes.ACC_NATIVE) != 0 && nativeSubstitutionClass != null) {
+        if (policy.isMethodRewriteBody) {
+            // If we are rewriting the entire method body, we need
+            // to convert native methods to non-native
             return access and Opcodes.ACC_NATIVE.inv()
         }
         return access
     }
 
     override fun visitMethodInner(
-            access: Int,
-            name: String,
-            descriptor: String,
-            signature: String?,
-            exceptions: Array<String>?,
-            policy: FilterPolicyWithReason,
-            substituted: Boolean,
-            superVisitor: MethodVisitor?,
+        access: Int,
+        name: String,
+        descriptor: String,
+        signature: String?,
+        exceptions: Array<String>?,
+        policy: FilterPolicyWithReason,
+        substituted: Boolean,
+        superVisitor: MethodVisitor?,
     ): MethodVisitor? {
-        // Inject method log, if needed.
         var innerVisitor = superVisitor
 
         //  If method logging is enabled, inject call to the logging method.
         val methodCallHooks = filter.getMethodCallHooks(currentClassName, name, descriptor)
         if (methodCallHooks.isNotEmpty()) {
             innerVisitor = MethodCallHookInjectingAdapter(
-                access,
                 name,
                 descriptor,
-                signature,
-                exceptions,
-                innerVisitor,
                 methodCallHooks,
-                )
+                innerVisitor,
+            )
         }
 
         // If this class already has a class initializer and a class load hook is needed, then
         // we inject code.
         if (classLoadHooks.isNotEmpty() &&
             name == CLASS_INITIALIZER_NAME &&
-            descriptor == CLASS_INITIALIZER_DESC) {
-            innerVisitor = ClassLoadHookInjectingMethodAdapter(
-                access,
-                name,
-                descriptor,
-                signature,
-                exceptions,
-                innerVisitor,
-            )
-        }
-
-        // If non-stub method call detection is enabled, then inject a call to the checker.
-        if (options.enableNonStubMethodCallDetection && doesMethodNeedNonStubCallCheck(
-                access, name, descriptor, policy) ) {
-            innerVisitor = NonStubMethodCallDetectingAdapter(
-                    access,
-                    name,
-                    descriptor,
-                    signature,
-                    exceptions,
-                    innerVisitor,
-            )
+            descriptor == CLASS_INITIALIZER_DESC
+        ) {
+            innerVisitor = ClassLoadHookInjectingMethodAdapter(innerVisitor)
         }
 
         fun MethodVisitor.withAnnotation(descriptor: String): MethodVisitor {
@@ -195,34 +170,31 @@
         }
 
         log.withIndent {
-            var willThrow = false
-            if (policy.policy == FilterPolicy.Throw) {
-                log.v("Making method throw...")
-                willThrow = true
-                innerVisitor = ThrowingMethodAdapter(
-                    access, name, descriptor, signature, exceptions, innerVisitor)
-                    .withAnnotation(HostStubGenProcessedAsThrow.CLASS_DESCRIPTOR)
+            // When we encounter native methods, we want to forcefully
+            // inject a method body. Also see [updateAccessFlags].
+            val forceCreateBody = (access and Opcodes.ACC_NATIVE) != 0
+            when (policy.policy) {
+                FilterPolicy.Throw -> {
+                    log.v("Making method throw...")
+                    return ThrowingMethodAdapter(forceCreateBody, innerVisitor)
+                        .withAnnotation(HostStubGenProcessedAsThrow.CLASS_DESCRIPTOR)
+                }
+                FilterPolicy.Ignore -> {
+                    log.v("Making method ignored...")
+                    return IgnoreMethodAdapter(descriptor, forceCreateBody, innerVisitor)
+                        .withAnnotation(HostStubGenProcessedAsIgnore.CLASS_DESCRIPTOR)
+                }
+                FilterPolicy.NativeSubstitute -> {
+                    log.v("Rewriting native method...")
+                    return NativeSubstitutingMethodAdapter(access, name, descriptor, innerVisitor)
+                        .withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR)
+                }
+                else -> {}
             }
-            if ((access and Opcodes.ACC_NATIVE) != 0 && nativeSubstitutionClass != null) {
-                log.v("Rewriting native method...")
-                return NativeSubstitutingMethodAdapter(
-                        access, name, descriptor, signature, exceptions, innerVisitor)
-                    .withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR)
-            }
-            if (willThrow) {
-                return innerVisitor
-            }
+        }
 
-            if (policy.policy == FilterPolicy.Ignore) {
-                log.v("Making method ignored...")
-                return IgnoreMethodAdapter(
-                    access, name, descriptor, signature, exceptions, innerVisitor)
-                    .withAnnotation(HostStubGenProcessedAsIgnore.CLASS_DESCRIPTOR)
-            }
-            if (filter.hasAnyMethodCallReplace()) {
-                innerVisitor = MethodCallReplacingAdapter(
-                    access, name, descriptor, signature, exceptions, innerVisitor)
-            }
+        if (filter.hasAnyMethodCallReplace()) {
+            innerVisitor = MethodCallReplacingAdapter(name, innerVisitor)
         }
         if (substituted) {
             innerVisitor?.withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR)
@@ -231,53 +203,32 @@
         return innerVisitor
     }
 
-    fun doesMethodNeedNonStubCallCheck(
-            access: Int,
-            name: String,
-            descriptor: String,
-            policy: FilterPolicyWithReason,
-    ): Boolean {
-        // If a method is in the stub, then no need to check.
-        if (policy.policy.needsInStub) {
-            return false
-        }
-        // If a method is private or package-private, no need to check.
-        // Technically test code can use framework package name, so it's a bit too lenient.
-        if (isVisibilityPrivateOrPackagePrivate(access)) {
-            return false
-        }
-        // TODO: If the method overrides a method that's accessible by tests, then we shouldn't
-        // do the check. (e.g. overrides a stub method or java standard method.)
-
-        return true
-    }
-
     /**
      * A method adapter that replaces the method body with a HostTestUtils.onThrowMethodCalled()
      * call.
      */
     private inner class ThrowingMethodAdapter(
-            access: Int,
-            val name: String,
-            descriptor: String,
-            signature: String?,
-            exceptions: Array<String>?,
-            next: MethodVisitor?
-    ) : BodyReplacingMethodVisitor(access, name, descriptor, signature, exceptions, next) {
+        createBody: Boolean,
+        next: MethodVisitor?
+    ) : BodyReplacingMethodVisitor(createBody, next) {
         override fun emitNewCode() {
-            visitMethodInsn(Opcodes.INVOKESTATIC,
-                    HostTestUtils.CLASS_INTERNAL_NAME,
-                    "onThrowMethodCalled",
-                    "()V",
-                    false)
+            visitMethodInsn(
+                INVOKESTATIC,
+                HostTestUtils.CLASS_INTERNAL_NAME,
+                "onThrowMethodCalled",
+                "()V",
+                false
+            )
 
             // We still need a RETURN opcode for the return type.
             // For now, let's just inject a `throw`.
             visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException")
             visitInsn(Opcodes.DUP)
             visitLdcInsn("Unreachable")
-            visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException",
-                    "<init>", "(Ljava/lang/String;)V", false)
+            visitMethodInsn(
+                Opcodes.INVOKESPECIAL, "java/lang/RuntimeException",
+                "<init>", "(Ljava/lang/String;)V", false
+            )
             visitInsn(Opcodes.ATHROW)
 
             // visitMaxs(3, if (isStatic) 0 else 1)
@@ -289,13 +240,10 @@
      * A method adapter that replaces the method body with a no-op return.
      */
     private inner class IgnoreMethodAdapter(
-            access: Int,
-            name: String,
-            val descriptor: String,
-            signature: String?,
-            exceptions: Array<String>?,
-            next: MethodVisitor?
-    ) : BodyReplacingMethodVisitor(access, name, descriptor, signature, exceptions, next) {
+        val descriptor: String,
+        createBody: Boolean,
+        next: MethodVisitor?
+    ) : BodyReplacingMethodVisitor(createBody, next) {
         override fun emitNewCode() {
             when (Type.getReturnType(descriptor)) {
                 Type.VOID_TYPE -> visitInsn(Opcodes.RETURN)
@@ -326,30 +274,24 @@
     }
 
     /**
-     * A method adapter that replaces a native method call with a call to the "native substitution"
-     * class.
+     * A method adapter that rewrite a native method body with a
+     * call to a method in the "native substitution" class.
      */
     private inner class NativeSubstitutingMethodAdapter(
-            val access: Int,
-            private val name: String,
-            private val descriptor: String,
-            signature: String?,
-            exceptions: Array<String>?,
-            next: MethodVisitor?
-    ) : MethodVisitor(OPCODE_VERSION, next) {
-        override fun visitCode() {
-            throw RuntimeException("NativeSubstitutingMethodVisitor should be called on " +
-                    " native method, where visitCode() shouldn't be called.")
-        }
+        access: Int,
+        private val name: String,
+        private val descriptor: String,
+        next: MethodVisitor?
+    ) : BodyReplacingMethodVisitor(true, next) {
 
-        override fun visitEnd() {
-            super.visitCode()
+        private val isStatic = (access and Opcodes.ACC_STATIC) != 0
 
+        override fun emitNewCode() {
             var targetDescriptor = descriptor
             var argOffset = 0
 
             // For non-static native method, we need to tweak it a bit.
-            if ((access and Opcodes.ACC_STATIC) == 0) {
+            if (!isStatic) {
                 // Push `this` as the first argument.
                 this.visitVarInsn(Opcodes.ALOAD, 0)
 
@@ -366,16 +308,17 @@
 
             writeByteCodeToPushArguments(descriptor, this, argOffset)
 
-            visitMethodInsn(Opcodes.INVOKESTATIC,
-                    nativeSubstitutionClass,
-                    name,
-                    targetDescriptor,
-                    false)
+            visitMethodInsn(
+                INVOKESTATIC,
+                nativeSubstitutionClass,
+                name,
+                targetDescriptor,
+                false
+            )
 
             writeByteCodeToReturn(descriptor, this)
 
             visitMaxs(99, 0) // We let ASM figure them out.
-            super.visitEnd()
         }
     }
 
@@ -386,25 +329,22 @@
      * `this(...)`. The logging code will be injected *before* such calls.
      */
     private inner class MethodCallHookInjectingAdapter(
-            access: Int,
-            val name: String,
-            val descriptor: String,
-            signature: String?,
-            exceptions: Array<String>?,
-            next: MethodVisitor?,
-            val hooks: List<String>,
+        val name: String,
+        val descriptor: String,
+        val hooks: List<String>,
+        next: MethodVisitor?,
     ) : MethodVisitor(OPCODE_VERSION, next) {
         override fun visitCode() {
             super.visitCode()
 
             hooks.forEach { hook ->
-                mv.visitLdcInsn(Type.getType("L" + currentClassName + ";"))
+                mv.visitLdcInsn(Type.getType("L$currentClassName;"))
                 visitLdcInsn(name)
                 visitLdcInsn(descriptor)
                 visitLdcInsn(hook)
 
                 visitMethodInsn(
-                    Opcodes.INVOKESTATIC,
+                    INVOKESTATIC,
                     HostTestUtils.CLASS_INTERNAL_NAME,
                     "callMethodCallHook",
                     "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
@@ -418,11 +358,6 @@
      * Inject a class load hook call.
      */
     private inner class ClassLoadHookInjectingMethodAdapter(
-        access: Int,
-        val name: String,
-        val descriptor: String,
-        signature: String?,
-        exceptions: Array<String>?,
         next: MethodVisitor?
     ) : MethodVisitor(OPCODE_VERSION, next) {
         override fun visitCode() {
@@ -432,53 +367,8 @@
         }
     }
 
-    /**
-     * A method adapter that detects calls to non-stub methods.
-     */
-    private inner class NonStubMethodCallDetectingAdapter(
-            access: Int,
-            val name: String,
-            val descriptor: String,
-            signature: String?,
-            exceptions: Array<String>?,
-            next: MethodVisitor?
-    ) : MethodVisitor(OPCODE_VERSION, next) {
-        override fun visitCode() {
-            super.visitCode()
-
-            // First three arguments to HostTestUtils.onNonStubMethodCalled().
-            visitLdcInsn(currentClassName)
-            visitLdcInsn(name)
-            visitLdcInsn(descriptor)
-
-            // Call: HostTestUtils.getStackWalker().getCallerClass().
-            // This push the caller Class in the stack.
-            visitMethodInsn(Opcodes.INVOKESTATIC,
-                    HostTestUtils.CLASS_INTERNAL_NAME,
-                    "getStackWalker",
-                    "()Ljava/lang/StackWalker;",
-                    false)
-            visitMethodInsn(Opcodes.INVOKEVIRTUAL,
-                    "java/lang/StackWalker",
-                    "getCallerClass",
-                    "()Ljava/lang/Class;",
-                    false)
-
-            // Then call onNonStubMethodCalled().
-            visitMethodInsn(Opcodes.INVOKESTATIC,
-                    HostTestUtils.CLASS_INTERNAL_NAME,
-                    "onNonStubMethodCalled",
-                    "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V",
-                    false)
-        }
-    }
-
     private inner class MethodCallReplacingAdapter(
-        access: Int,
         val callerMethodName: String,
-        val descriptor: String,
-        signature: String?,
-        exceptions: Array<String>?,
         next: MethodVisitor?,
     ) : MethodVisitor(OPCODE_VERSION, next) {
         override fun visitMethodInsn(
@@ -497,7 +387,8 @@
                 }
             }
             val to = filter.getMethodCallReplaceTo(
-                currentClassName, callerMethodName, owner!!, name!!, descriptor!!)
+                currentClassName, callerMethodName, owner!!, name!!, descriptor!!
+            )
 
             if (to == null
                 // Don't replace if the target is the callsite.
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/StubGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/StubGeneratingAdapter.kt
deleted file mode 100644
index fc20f28..0000000
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/StubGeneratingAdapter.kt
+++ /dev/null
@@ -1,86 +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.hoststubgen.visitors
-
-import com.android.hoststubgen.asm.ClassNodes
-import com.android.hoststubgen.filters.FilterPolicy
-import com.android.hoststubgen.filters.FilterPolicyWithReason
-import com.android.hoststubgen.filters.OutputFilter
-import com.android.hoststubgen.log
-import org.objectweb.asm.ClassVisitor
-import org.objectweb.asm.MethodVisitor
-import org.objectweb.asm.Opcodes
-
-/**
- * An adapter that generates the "impl" class file from an input class file.
- */
-class StubGeneratingAdapter(
-        classes: ClassNodes,
-        nextVisitor: ClassVisitor,
-        filter: OutputFilter,
-        options: Options,
-) : BaseAdapter(classes, nextVisitor, filter, options) {
-
-    override fun shouldEmit(policy: FilterPolicy): Boolean {
-        return policy.needsInStub
-    }
-
-    override fun visitMethodInner(
-            access: Int,
-            name: String,
-            descriptor: String,
-            signature: String?,
-            exceptions: Array<String>?,
-            policy: FilterPolicyWithReason,
-            substituted: Boolean,
-            superVisitor: MethodVisitor?,
-    ): MethodVisitor? {
-        return StubMethodVisitor(access, name, descriptor, signature, exceptions, superVisitor)
-    }
-
-    private inner class StubMethodVisitor(
-            access: Int,
-            val name: String,
-            descriptor: String,
-            signature: String?,
-            exceptions: Array<String>?,
-            next: MethodVisitor?
-    ) : BodyReplacingMethodVisitor(access, name, descriptor, signature, exceptions, next) {
-        override fun emitNewCode() {
-            log.d("  Generating stub method for $currentClassName.$name")
-
-            // Inject the following code:
-            //   throw new RuntimeException("Stub!");
-
-            /*
-                NEW java/lang/RuntimeException
-                DUP
-                LDC "not supported on host side"
-                INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/String;)V
-                ATHROW
-                MAXSTACK = 3
-                MAXLOCALS = 2 <- 1 for this, 1 for return value.
-             */
-            visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException")
-            visitInsn(Opcodes.DUP)
-            visitLdcInsn("Stub!")
-            visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException",
-                    "<init>", "(Ljava/lang/String;)V", false)
-            visitInsn(Opcodes.ATHROW)
-            visitMaxs(0, 0) // We let ASM figure them out.
-        }
-    }
-}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
index e7873d6..ba2c869 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
@@ -21,7 +21,7 @@
 
 // Create stub/impl jars from "hoststubgen-test-tiny-framework", using the following 3 rules.
 java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host",
+    name: "hoststubgen-test-tiny-framework-host-base",
     defaults: ["hoststubgen-command-defaults"],
     cmd: hoststubgen_common_options +
         "--in-jar $(location :hoststubgen-test-tiny-framework) " +
@@ -35,25 +35,13 @@
 }
 
 java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host-stub",
+    name: "hoststubgen-test-tiny-framework-host",
     cmd: "cp $(in) $(out)",
     srcs: [
-        ":hoststubgen-test-tiny-framework-host{host_stub.jar}",
+        ":hoststubgen-test-tiny-framework-host-base{host.jar}",
     ],
     out: [
-        "host_stub.jar",
-    ],
-    visibility: ["//visibility:private"],
-}
-
-java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host-impl",
-    cmd: "cp $(in) $(out)",
-    srcs: [
-        ":hoststubgen-test-tiny-framework-host{host_impl.jar}",
-    ],
-    out: [
-        "host_impl.jar",
+        "host.jar",
     ],
     visibility: ["//visibility:private"],
 }
@@ -61,7 +49,7 @@
 // Same as "hoststubgen-test-tiny-framework-host", but with more options, to test more hoststubgen
 // features.
 java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host-ext",
+    name: "hoststubgen-test-tiny-framework-host-ext-base",
     defaults: ["hoststubgen-command-defaults"],
     cmd: hoststubgen_common_options +
         "--in-jar $(location :hoststubgen-test-tiny-framework) " +
@@ -79,37 +67,25 @@
 }
 
 java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host-ext-stub",
+    name: "hoststubgen-test-tiny-framework-host-ext",
     cmd: "cp $(in) $(out)",
     srcs: [
-        ":hoststubgen-test-tiny-framework-host-ext{host_stub.jar}",
+        ":hoststubgen-test-tiny-framework-host-ext-base{host.jar}",
     ],
     out: [
-        "host_stub.jar",
-    ],
-    visibility: ["//visibility:private"],
-}
-
-java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host-ext-impl",
-    cmd: "cp $(in) $(out)",
-    srcs: [
-        ":hoststubgen-test-tiny-framework-host-ext{host_impl.jar}",
-    ],
-    out: [
-        "host_impl.jar",
+        "host.jar",
     ],
     visibility: ["//visibility:private"],
 }
 
 // Compile the test jar, using 2 rules.
-// 1. Build the test against the stub.
+// 1. Build the test against the original framework.
 java_library_host {
     name: "hoststubgen-test-tiny-test-lib",
     srcs: ["tiny-test/src/**/*.java"],
 
     libs: [
-        "hoststubgen-test-tiny-framework-host-stub",
+        "hoststubgen-test-tiny-framework",
     ],
     static_libs: [
         "junit",
@@ -129,7 +105,7 @@
     static_libs: [
         "hoststubgen-test-tiny-test-lib",
         "hoststubgen-helper-runtime",
-        "hoststubgen-test-tiny-framework-host-impl",
+        "hoststubgen-test-tiny-framework-host",
     ],
     test_suites: ["general-tests"],
 }
@@ -149,49 +125,25 @@
 }
 
 java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host-stub-dump",
+    name: "hoststubgen-test-tiny-framework-host-dump",
     defaults: ["hoststubgen-jar-dump-defaults"],
     srcs: [
-        ":hoststubgen-test-tiny-framework-host-stub",
+        ":hoststubgen-test-tiny-framework-host",
     ],
     out: [
-        "02-hoststubgen-test-tiny-framework-host-stub-dump.txt",
+        "03-hoststubgen-test-tiny-framework-host-dump.txt",
     ],
     visibility: ["//visibility:private"],
 }
 
 java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host-impl-dump",
+    name: "hoststubgen-test-tiny-framework-host-ext-dump",
     defaults: ["hoststubgen-jar-dump-defaults"],
     srcs: [
-        ":hoststubgen-test-tiny-framework-host-impl",
+        ":hoststubgen-test-tiny-framework-host-ext",
     ],
     out: [
-        "03-hoststubgen-test-tiny-framework-host-impl-dump.txt",
-    ],
-    visibility: ["//visibility:private"],
-}
-
-java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host-ext-stub-dump",
-    defaults: ["hoststubgen-jar-dump-defaults"],
-    srcs: [
-        ":hoststubgen-test-tiny-framework-host-ext-stub",
-    ],
-    out: [
-        "12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt",
-    ],
-    visibility: ["//visibility:private"],
-}
-
-java_genrule_host {
-    name: "hoststubgen-test-tiny-framework-host-ext-impl-dump",
-    defaults: ["hoststubgen-jar-dump-defaults"],
-    srcs: [
-        ":hoststubgen-test-tiny-framework-host-ext-impl",
-    ],
-    out: [
-        "13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt",
+        "13-hoststubgen-test-tiny-framework-host-ext-dump.txt",
     ],
     visibility: ["//visibility:private"],
 }
@@ -206,11 +158,9 @@
         "golden-output/*.txt",
     ],
     java_data: [
-        "hoststubgen-test-tiny-framework-host-stub-dump",
-        "hoststubgen-test-tiny-framework-host-impl-dump",
         "hoststubgen-test-tiny-framework-orig-dump",
-        "hoststubgen-test-tiny-framework-host-ext-stub-dump",
-        "hoststubgen-test-tiny-framework-host-ext-impl-dump",
+        "hoststubgen-test-tiny-framework-host-dump",
+        "hoststubgen-test-tiny-framework-host-ext-dump",
     ],
     test_suites: ["general-tests"],
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
index bd9e85e..de4cb0c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt
@@ -6,10 +6,10 @@
 
 
 # To allow a specific class to use annotations:
-# com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+# com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
 
 # To disallow a specific class to use annotations:
-# !com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+# !com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
 
 # To allow a specific package to use annotations:
 # com.android.hoststubgen.test.*
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index c2f593c..5fde14f 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -104,26 +104,6 @@
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
-## Class: android/hosttest/annotation/HostSideTestStub.class
-  Compiled from "HostSideTestStub.java"
-public interface android.hosttest.annotation.HostSideTestStub extends java.lang.annotation.Annotation
-  minor version: 0
-  major version: 61
-  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
-  this_class: #x                          // android/hosttest/annotation/HostSideTestStub
-  super_class: #x                         // java/lang/Object
-  interfaces: 1, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "HostSideTestStub.java"
-RuntimeVisibleAnnotations:
-  x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
-    java.lang.annotation.Target(
-      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
-    )
-  x: #x(#x=e#x.#x)
-    java.lang.annotation.Retention(
-      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
-    )
 ## Class: android/hosttest/annotation/HostSideTestSubstitute.class
   Compiled from "HostSideTestSubstitute.java"
 public interface android.hosttest.annotation.HostSideTestSubstitute extends java.lang.annotation.Annotation
@@ -187,26 +167,6 @@
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
-## Class: android/hosttest/annotation/HostSideTestWholeClassStub.class
-  Compiled from "HostSideTestWholeClassStub.java"
-public interface android.hosttest.annotation.HostSideTestWholeClassStub extends java.lang.annotation.Annotation
-  minor version: 0
-  major version: 61
-  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
-  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassStub
-  super_class: #x                         // java/lang/Object
-  interfaces: 1, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "HostSideTestWholeClassStub.java"
-RuntimeVisibleAnnotations:
-  x: #x(#x=[e#x.#x])
-    java.lang.annotation.Target(
-      value=[Ljava/lang/annotation/ElementType;.TYPE]
-    )
-  x: #x(#x=e#x.#x)
-    java.lang.annotation.Retention(
-      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
-    )
 ## Class: android/hosttest/annotation/tests/HostSideTestSuppress.class
   Compiled from "HostSideTestSuppress.java"
 public interface android.hosttest.annotation.tests.HostSideTestSuppress extends java.lang.annotation.Annotation
@@ -394,120 +354,15 @@
   com/android/hoststubgen/test/tinyframework/R$Nested
 InnerClasses:
   public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
-  minor version: 0
-  major version: 61
-  flags: (0x0020) ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 3
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
-    descriptor: ()V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         x: return
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl;
-
-  public static int getOneKeep();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=1, locals=0, args_size=0
-         x: iconst_1
-         x: ireturn
-      LineNumberTable:
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestKeep
-
-  public static int getOneStub();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=1, locals=0, args_size=0
-         x: iconst_1
-         x: ireturn
-      LineNumberTable:
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-}
-SourceFile: "TinyFrameworkCallerCheck.java"
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-InnerClasses:
-  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+  Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
   minor version: 0
   major version: 61
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         x: return
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck;
-
-  public static int getOne_withCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=1, locals=0, args_size=0
-         x: invokestatic  #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
-         x: ireturn
-      LineNumberTable:
-
-  public static int getOne_noCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=1, locals=0, args_size=0
-         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I
-         x: ireturn
-      LineNumberTable:
-}
-SourceFile: "TinyFrameworkCallerCheck.java"
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-InnerClasses:
-  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
-  Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 3, methods: 10, attributes: 2
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
+  interfaces: 0, fields: 2, methods: 8, attributes: 2
   public int keep;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -519,7 +374,7 @@
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
 
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
@@ -528,42 +383,21 @@
          x: invokespecial #x                  // Method java/lang/Object."<init>":()V
          x: aload_0
          x: iconst_1
-         x: putfield      #x                  // Field stub:I
-         x: aload_0
-        x: iconst_2
-        x: putfield      #x                 // Field keep:I
-        x: return
+         x: putfield      #x                  // Field keep:I
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public int addOne(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         x: aload_0
-         x: iload_1
-         x: invokevirtual #x                 // Method addOneInner:(I)I
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
-            0       6     1 value   I
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
          x: iload_1
          x: iconst_1
          x: iadd
@@ -571,7 +405,7 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
             0       4     1 value   I
     RuntimeInvisibleAnnotations:
       x: #x()
@@ -589,7 +423,7 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
             0       8     1   foo   Ljava/lang/String;
     RuntimeInvisibleAnnotations:
       x: #x()
@@ -608,11 +442,9 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
             0      10     1 value   I
     RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
       x: #x(#x=s#x)
         android.hosttest.annotation.HostSideTestSubstitute(
           suffix="_host"
@@ -630,15 +462,13 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
             0       4     1 value   I
 
   public static native int nativeAddThree(int);
     descriptor: (I)I
     flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
     RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
       x: #x(#x=s#x)
         android.hosttest.annotation.HostSideTestSubstitute(
           suffix="_host"
@@ -668,212 +498,19 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestThrow
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
 }
-SourceFile: "TinyFrameworkClassAnnotations.java"
+SourceFile: "TinyFrameworkAnnotations.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
-  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 3, methods: 10, attributes: 2
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-
-  public int keep;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-
-  public int remove;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=1, args_size=1
-         x: aload_0
-         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
-         x: aload_0
-         x: iconst_1
-         x: putfield      #x                  // Field stub:I
-         x: aload_0
-        x: iconst_2
-        x: putfield      #x                 // Field keep:I
-        x: return
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-
-  public int addOne(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
-         x: aload_0
-         x: iload_1
-         x: invokevirtual #x                 // Method addOneInner:(I)I
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-            0       6     1 value   I
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
-         x: iload_1
-         x: iconst_1
-         x: iadd
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-            0       4     1 value   I
-
-  public void toBeRemoved(java.lang.String);
-    descriptor: (Ljava/lang/String;)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
-         x: athrow
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-            0       8     1   foo   Ljava/lang/String;
-
-  public int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String not supported on host side
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-            0      10     1 value   I
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-      x: #x(#x=s#x)
-        android.hosttest.annotation.HostSideTestSubstitute(
-          suffix="_host"
-        )
-
-  public int addTwo_host(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
-         x: iload_1
-         x: iconst_2
-         x: iadd
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-            0       4     1 value   I
-
-  public static native int nativeAddThree(int);
-    descriptor: (I)I
-    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-      x: #x(#x=s#x)
-        android.hosttest.annotation.HostSideTestSubstitute(
-          suffix="_host"
-        )
-
-  public static int nativeAddThree_host(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=2, locals=1, args_size=1
-         x: iload_0
-         x: iconst_3
-         x: iadd
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       4     0 value   I
-
-  public java.lang.String unsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
-         x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
   Compiled from "TinyFrameworkClassLoadHook.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
@@ -935,7 +572,131 @@
 SourceFile: "TinyFrameworkClassLoadHook.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 6, attributes: 2
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRemove
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                  // Field keep:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       4     1 value   I
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String not supported on host side
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0      10     1 value   I
+    RuntimeInvisibleAnnotations:
+      x: #x(#x=s#x)
+        android.hosttest.annotation.HostSideTestSubstitute(
+          suffix="_host"
+        )
+
+  public int addTwo_host(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       4     1 value   I
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       8     1   foo   Ljava/lang/String;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestRemove
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         x: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
   Compiled from "TinyFrameworkClassWithInitializerDefault.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
@@ -950,14 +711,14 @@
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.lang.Object sObject;
     descriptor: Ljava/lang/Object;
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault();
     descriptor: ()V
@@ -989,7 +750,7 @@
 SourceFile: "TinyFrameworkClassWithInitializerDefault.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.class
   Compiled from "TinyFrameworkClassWithInitializerStub.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub
@@ -1004,14 +765,14 @@
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.lang.Object sObject;
     descriptor: Ljava/lang/Object;
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub();
     descriptor: ()V
@@ -1047,7 +808,7 @@
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
@@ -1064,21 +825,21 @@
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private final java.lang.String mLongName;
     descriptor: Ljava/lang/String;
@@ -1158,7 +919,7 @@
     Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.lang.String getLongName();
     descriptor: ()Ljava/lang/String;
@@ -1174,7 +935,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.lang.String getShortName();
     descriptor: ()Ljava/lang/String;
@@ -1190,7 +951,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
@@ -1252,7 +1013,7 @@
 SourceFile: "TinyFrameworkEnumComplex.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
   Compiled from "TinyFrameworkEnumSimple.java"
 public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
@@ -1267,14 +1028,14 @@
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
     descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -1373,7 +1134,7 @@
 SourceFile: "TinyFrameworkEnumSimple.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
@@ -1427,7 +1188,7 @@
 SourceFile: "TinyFrameworkExceptionTester.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
   Compiled from "TinyFrameworkForTextPolicy.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
@@ -1436,15 +1197,11 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 3, methods: 19, attributes: 1
+  interfaces: 0, fields: 2, methods: 17, attributes: 1
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
 
-  public int keep;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-
   public int remove;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -1459,35 +1216,17 @@
          x: aload_0
          x: iconst_1
          x: putfield      #x                  // Field stub:I
-         x: aload_0
-        x: iconst_2
-        x: putfield      #x                 // Field keep:I
-        x: return
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
 
   public int addOne(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         x: aload_0
-         x: iload_1
-         x: invokevirtual #x                 // Method addOneInner:(I)I
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
-            0       6     1 value   I
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
          x: iload_1
          x: iconst_1
          x: iadd
@@ -1699,19 +1438,6 @@
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
             0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
 }
 SourceFile: "TinyFrameworkForTextPolicy.java"
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.class
@@ -1729,7 +1455,7 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
     descriptor: Ljava/util/function/Supplier;
@@ -1737,7 +1463,7 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested();
     descriptor: ()V
@@ -1756,7 +1482,7 @@
             0      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.util.function.Supplier<java.lang.Integer> getSupplier();
     descriptor: ()Ljava/util/function/Supplier;
@@ -1772,7 +1498,7 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -1785,7 +1511,7 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static java.lang.Integer lambda$getSupplier_static$3();
     descriptor: ()Ljava/lang/Integer;
@@ -1840,7 +1566,7 @@
 SourceFile: "TinyFrameworkLambdas.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
@@ -1883,7 +1609,7 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
     descriptor: Ljava/util/function/Supplier;
@@ -1891,7 +1617,7 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas();
     descriptor: ()V
@@ -1910,7 +1636,7 @@
             0      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.util.function.Supplier<java.lang.Integer> getSupplier();
     descriptor: ()Ljava/util/function/Supplier;
@@ -1926,7 +1652,7 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -1939,7 +1665,7 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static java.lang.Integer lambda$getSupplier_static$3();
     descriptor: ()Ljava/lang/Integer;
@@ -1994,7 +1720,7 @@
 SourceFile: "TinyFrameworkLambdas.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 NestMembers:
@@ -2179,7 +1905,7 @@
 SourceFile: "TinyFrameworkMethodCallReplace.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
 BootstrapMethods:
@@ -2199,7 +1925,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 11, attributes: 2
+  interfaces: 0, fields: 1, methods: 12, attributes: 2
   int value;
     descriptor: I
     flags: (0x0000)
@@ -2294,6 +2020,13 @@
       x: #x()
         android.hosttest.annotation.HostSideTestThrow
 
+  public static native void nativeStillKeep();
+    descriptor: ()V
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
   public static void nativeStillNotSupported_should_be_like_this();
     descriptor: ()V
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -2312,7 +2045,7 @@
 SourceFile: "TinyFrameworkNative.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
   x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
       value="TinyFrameworkNative_host"
@@ -2684,7 +2417,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 1, attributes: 4
+  interfaces: 0, fields: 2, methods: 1, attributes: 3
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2717,9 +2450,6 @@
       <no name>                      final mandated
 }
 SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 InnerClasses:
   public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
@@ -2778,6 +2508,40 @@
 InnerClasses:
   public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 3
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: bipush        8
+         x: putfield      #x                  // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass;
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
@@ -2786,7 +2550,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  interfaces: 0, fields: 1, methods: 2, attributes: 3
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2820,13 +2584,11 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
 }
 SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 InnerClasses:
   public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
@@ -2937,11 +2699,12 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
@@ -2957,6 +2720,7 @@
   public static #x= #x of #x;          // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public #x= #x of #x;                 // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.class
   Compiled from "TinyFrameworkPackageRedirect.java"
@@ -2999,7 +2763,7 @@
 SourceFile: "TinyFrameworkPackageRedirect.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
   Compiled from "TinyFrameworkRenamedClassCaller.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
@@ -3041,7 +2805,7 @@
 SourceFile: "TinyFrameworkRenamedClassCaller.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
   Compiled from "TinyFrameworkToBeRenamed.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
@@ -3088,7 +2852,7 @@
 SourceFile: "TinyFrameworkToBeRenamed.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
   Compiled from "A.java"
 public class com.android.hoststubgen.test.tinyframework.packagetest.A
@@ -3834,4 +3598,4 @@
 SourceFile: "UnsupportedClass.java"
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
deleted file mode 100644
index 1b83d24..0000000
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ /dev/null
@@ -1,2829 +0,0 @@
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
-  Compiled from "IPretendingAidl.java"
-public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-SourceFile: "IPretendingAidl.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
-  Compiled from "IPretendingAidl.java"
-public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int addOne(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-SourceFile: "IPretendingAidl.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
-  Compiled from "IPretendingAidl.java"
-public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 4
-}
-InnerClasses:
-  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-SourceFile: "IPretendingAidl.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
-## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
-  Compiled from "R.java"
-public class com.android.hoststubgen.test.tinyframework.R$Nested
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R$Nested
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 4
-  public static int[] ARRAY;
-    descriptor: [I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.R$Nested();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
-SourceFile: "R.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/R
-## Class: com/android/hoststubgen/test/tinyframework/R.class
-  Compiled from "R.java"
-public class com.android.hoststubgen.test.tinyframework.R
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 1, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.R();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
-SourceFile: "R.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/R$Nested
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
-  minor version: 0
-  major version: 61
-  flags: (0x0020) ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 4
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
-    descriptor: ()V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOneStub();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-}
-InnerClasses:
-  private static #x= #x of #x;           // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-SourceFile: "TinyFrameworkCallerCheck.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 5
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOne_withCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOne_noCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-SourceFile: "TinyFrameworkCallerCheck.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
-  Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 5, attributes: 3
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public int addOne(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddThree(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-}
-SourceFile: "TinyFrameworkClassAnnotations.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-  x: #x(#x=s#x)
-    android.hosttest.annotation.HostSideTestClassLoadHook(
-      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
-    )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
-  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 3, methods: 8, attributes: 3
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int keep;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int remove;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOne(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public void toBeRemoved(java.lang.String);
-    descriptor: (Ljava/lang/String;)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddThree(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String unsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
-  Compiled from "TinyFrameworkClassLoadHook.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 3, attributes: 3
-  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
-    descriptor: Ljava/util/Set;
-    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
-    descriptor: ()V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static void onClassLoaded(java.lang.Class<?>);
-    descriptor: (Ljava/lang/Class;)V
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // (Ljava/lang/Class<*>;)V
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassLoadHook.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
-  Compiled from "TinyFrameworkClassWithInitializerDefault.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 0, attributes: 3
-  public static boolean sInitialized;
-    descriptor: Z
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static java.lang.Object sObject;
-    descriptor: Ljava/lang/Object;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-}
-SourceFile: "TinyFrameworkClassWithInitializerDefault.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.class
-  Compiled from "TinyFrameworkClassWithInitializerStub.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 0, attributes: 3
-  public static boolean sInitialized;
-    descriptor: Z
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static java.lang.Object sObject;
-    descriptor: Ljava/lang/Object;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-}
-SourceFile: "TinyFrameworkClassWithInitializerStub.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x(#x=s#x)
-    android.hosttest.annotation.HostSideTestClassLoadHook(
-      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
-    )
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-  x: #x()
-    android.hosttest.annotation.HostSideTestStaticInitializerKeep
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
-  Compiled from "TinyFrameworkEnumComplex.java"
-public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
-  minor version: 0
-  major version: 61
-  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
-  super_class: #x                         // java/lang/Enum
-  interfaces: 0, fields: 4, methods: 7, attributes: 4
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
-    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
-    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
-    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    MethodParameters:
-      Name                           Flags
-      <no name>                      mandated
-
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
-    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=3, locals=5, args_size=5
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-    MethodParameters:
-      Name                           Flags
-      <no name>                      synthetic
-      <no name>                      synthetic
-      <no name>
-      <no name>
-
-  public java.lang.String getLongName();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public java.lang.String getShortName();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
-    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
-SourceFile: "TinyFrameworkEnumComplex.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
-  Compiled from "TinyFrameworkEnumSimple.java"
-public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
-  minor version: 0
-  major version: 61
-  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
-  super_class: #x                         // java/lang/Enum
-  interfaces: 0, fields: 3, methods: 5, attributes: 4
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
-    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
-    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
-    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    MethodParameters:
-      Name                           Flags
-      <no name>                      mandated
-
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
-    descriptor: (Ljava/lang/String;I)V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=3, locals=3, args_size=3
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()V
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    MethodParameters:
-      Name                           Flags
-      <no name>                      synthetic
-      <no name>                      synthetic
-
-  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
-    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
-SourceFile: "TinyFrameworkEnumSimple.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
-  Compiled from "TinyFrameworkExceptionTester.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 3
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int testException();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkExceptionTester.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
-  Compiled from "TinyFrameworkForTextPolicy.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 14, attributes: 2
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOne(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String toBeIgnoredObj();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public void toBeIgnoredV();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public boolean toBeIgnoredZ();
-    descriptor: ()Z
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public byte toBeIgnoredB();
-    descriptor: ()B
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public char toBeIgnoredC();
-    descriptor: ()C
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public short toBeIgnoredS();
-    descriptor: ()S
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int toBeIgnoredI();
-    descriptor: ()I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public float toBeIgnoredF();
-    descriptor: ()F
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public double toBeIgnoredD();
-    descriptor: ()D
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddThree(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkForTextPolicy.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.class
-  Compiled from "TinyFrameworkLambdas.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 7, attributes: 5
-  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public java.util.function.Supplier<java.lang.Integer> getSupplier();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static java.lang.Integer lambda$getSupplier_static$3();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$getSupplier$2();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$static$1();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$new$0();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
-  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
-SourceFile: "TinyFrameworkLambdas.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-  x: #x()
-    android.hosttest.annotation.HostSideTestStaticInitializerKeep
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.class
-  Compiled from "TinyFrameworkLambdas.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 7, attributes: 5
-  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public java.util.function.Supplier<java.lang.Integer> getSupplier();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static java.lang.Integer lambda$getSupplier_static$3();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$getSupplier$2();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$static$1();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$new$0();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
-  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
-SourceFile: "TinyFrameworkLambdas.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-  x: #x()
-    android.hosttest.annotation.HostSideTestStaticInitializerKeep
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
-  Compiled from "TinyFrameworkMethodCallReplace.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static void startThread(java.lang.Thread);
-    descriptor: (Ljava/lang/Thread;)V
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int add(int, int);
-    descriptor: (II)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
-SourceFile: "TinyFrameworkMethodCallReplace.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
-  Compiled from "TinyFrameworkMethodCallReplace.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 4, attributes: 5
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
-    descriptor: ()Z
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Exceptions:
-      throws java.lang.Exception
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int staticMethodCallReplaceTester();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
-    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
-  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
-SourceFile: "TinyFrameworkMethodCallReplace.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
-  Compiled from "TinyFrameworkNative.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 10, attributes: 3
-  int value;
-    descriptor: I
-    flags: (0x0000)
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static native int nativeAddTwo(int);
-    descriptor: (I)I
-    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddTwo_should_be_like_this(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static native long nativeLongPlus(long, long);
-    descriptor: (JJ)J
-    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static long nativeLongPlus_should_be_like_this(long, long);
-    descriptor: (JJ)J
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=4, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public void setValue(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public native int nativeNonStaticAddToValue(int);
-    descriptor: (I)I
-    flags: (0x0101) ACC_PUBLIC, ACC_NATIVE
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int nativeNonStaticAddToValue_should_be_like_this(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static void nativeStillNotSupported_should_be_like_this();
-    descriptor: ()V
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static native byte nativeBytePlus(byte, byte);
-    descriptor: (BB)B
-    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkNative.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-  x: #x(#x=s#x)
-    android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
-      value="TinyFrameworkNative_host"
-    )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 1, attributes: 4
-  public int value;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 1, attributes: 5
-  public int value;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
-    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
-    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    MethodParameters:
-      Name                           Flags
-      <no name>                      final mandated
-}
-InnerClasses:
-  public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 5
-  public int value;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
-  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
-  interfaces: 0, fields: 0, methods: 1, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 4, attributes: 5
-  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.util.function.Supplier<java.lang.Integer> getSupplier();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.class
-  Compiled from "TinyFrameworkPackageRedirect.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 3
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int foo(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkPackageRedirect.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
-  Compiled from "TinyFrameworkRenamedClassCaller.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 3
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int foo(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkRenamedClassCaller.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
-  Compiled from "A.java"
-public class com.android.hoststubgen.test.tinyframework.packagetest.A
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "A.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
-  Compiled from "A.java"
-public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "A.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
-  Compiled from "C1.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C1
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "C1.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C2.class
-  Compiled from "C2.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C2
-  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "C2.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C3.class
-  Compiled from "C3.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C3
-  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "C3.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CA.class
-  Compiled from "CA.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CA
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "CA.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CB.class
-  Compiled from "CB.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CB
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "CB.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I1.class
-  Compiled from "I1.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I1
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "I1.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
-  Compiled from "I2.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 extends com.android.hoststubgen.test.tinyframework.subclasstest.I1
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I2
-  super_class: #x                         // java/lang/Object
-  interfaces: 1, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "I2.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
-  Compiled from "I3.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 extends com.android.hoststubgen.test.tinyframework.subclasstest.I2
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I3
-  super_class: #x                         // java/lang/Object
-  interfaces: 1, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "I3.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
-  Compiled from "IA.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IA
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "IA.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
-  Compiled from "IB.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IB
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "IB.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/unsupported/UnsupportedClass.class
-  Compiled from "UnsupportedClass.java"
-public class com.unsupported.UnsupportedClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/unsupported/UnsupportedClass
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 3
-  public com.unsupported.UnsupportedClass(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int getValue();
-    descriptor: ()I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "UnsupportedClass.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
-  Compiled from "TinyFrameworkToBeRenamed.java"
-public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 3
-  private final int mValue;
-    descriptor: I
-    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int getValue();
-    descriptor: ()I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkToBeRenamed.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt
similarity index 67%
rename from tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
rename to tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt
index d23b450..e41d46d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt
@@ -12,12 +12,12 @@
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "HostSideTestClassLoadHook.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
@@ -39,7 +39,7 @@
 SourceFile: "HostSideTestKeep.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
@@ -62,12 +62,12 @@
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "HostSideTestNativeSubstitutionClass.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
@@ -89,7 +89,7 @@
 SourceFile: "HostSideTestRemove.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
@@ -98,20 +98,20 @@
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
-## Class: android/hosttest/annotation/HostSideTestStub.class
-  Compiled from "HostSideTestStub.java"
-public interface android.hosttest.annotation.HostSideTestStub extends java.lang.annotation.Annotation
+## Class: android/hosttest/annotation/HostSideTestStaticInitializerKeep.class
+  Compiled from "HostSideTestStaticInitializerKeep.java"
+public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep extends java.lang.annotation.Annotation
   minor version: 0
   major version: 61
   flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
-  this_class: #x                          // android/hosttest/annotation/HostSideTestStub
+  this_class: #x                          // android/hosttest/annotation/HostSideTestStaticInitializerKeep
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 0, attributes: 2
 }
-SourceFile: "HostSideTestStub.java"
+SourceFile: "HostSideTestStaticInitializerKeep.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
@@ -134,12 +134,12 @@
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "HostSideTestSubstitute.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.METHOD]
@@ -161,7 +161,7 @@
 SourceFile: "HostSideTestThrow.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
@@ -183,29 +183,7 @@
 SourceFile: "HostSideTestWholeClassKeep.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-  x: #x(#x=[e#x.#x])
-    java.lang.annotation.Target(
-      value=[Ljava/lang/annotation/ElementType;.TYPE]
-    )
-  x: #x(#x=e#x.#x)
-    java.lang.annotation.Retention(
-      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
-    )
-## Class: android/hosttest/annotation/HostSideTestWholeClassStub.class
-  Compiled from "HostSideTestWholeClassStub.java"
-public interface android.hosttest.annotation.HostSideTestWholeClassStub extends java.lang.annotation.Annotation
-  minor version: 0
-  major version: 61
-  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
-  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassStub
-  super_class: #x                         // java/lang/Object
-  interfaces: 1, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "HostSideTestWholeClassStub.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
@@ -237,9 +215,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int addTwo(int);
     descriptor: (I)I
@@ -256,9 +232,7 @@
             0       4     0     a   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
@@ -266,9 +240,7 @@
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
 ## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
   Compiled from "IPretendingAidl.java"
@@ -293,9 +265,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int addOne(int);
     descriptor: (I)I
@@ -312,19 +282,15 @@
             0       4     0     a   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
-  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
+  public static #x= #x of #x;             // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
   public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
 ## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
   Compiled from "IPretendingAidl.java"
@@ -342,9 +308,7 @@
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
@@ -362,9 +326,7 @@
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public com.android.hoststubgen.test.tinyframework.R$Nested();
     descriptor: ()V
@@ -380,9 +342,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R$Nested;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -400,18 +360,14 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
-  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+  public static #x= #x of #x;             // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
 SourceFile: "R.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/R
 ## Class: com/android/hoststubgen/test/tinyframework/R.class
   Compiled from "R.java"
@@ -436,189 +392,31 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
-  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+  public static #x= #x of #x;             // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
 SourceFile: "R.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/R$Nested
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
-  minor version: 0
-  major version: 61
-  flags: (0x0020) ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 4
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
-    descriptor: ()V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         x: return
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOneKeep();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=4, locals=0, args_size=0
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-         x: ldc           #x                 // String getOneKeep
-         x: ldc           #x                 // String ()I
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iconst_1
-        x: ireturn
-      LineNumberTable:
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestKeep
-
-  public static int getOneStub();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=1, locals=0, args_size=0
-         x: iconst_1
-         x: ireturn
-      LineNumberTable:
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-}
-InnerClasses:
-  private static #x= #x of #x;           // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-SourceFile: "TinyFrameworkCallerCheck.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+  Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
   minor version: 0
   major version: 61
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 5
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         x: return
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOne_withCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=1, locals=0, args_size=0
-         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
-         x: ireturn
-      LineNumberTable:
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOne_noCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=1, locals=0, args_size=0
-         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I
-         x: ireturn
-      LineNumberTable:
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-SourceFile: "TinyFrameworkCallerCheck.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
-  Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 8, attributes: 3
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
+  interfaces: 0, fields: 1, methods: 6, attributes: 3
   public int keep;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
@@ -628,12 +426,12 @@
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-         x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+         x: ldc           #x                  // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
@@ -642,70 +440,36 @@
          x: invokespecial #x                 // Method java/lang/Object."<init>":()V
          x: aload_0
          x: iconst_1
-         x: putfield      #x                 // Field stub:I
-         x: aload_0
-        x: iconst_2
-        x: putfield      #x                 // Field keep:I
-        x: return
+         x: putfield      #x                 // Field keep:I
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public int addOne(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         x: aload_0
          x: iload_1
-         x: invokevirtual #x                 // Method addOneInner:(I)I
+         x: iconst_1
+         x: iadd
          x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
-            0       6     1 value   I
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+            0       4     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-         x: ldc           #x                 // String addOneInner
-         x: ldc           #x                 // String (I)I
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iload_1
-        x: iconst_1
-        x: iadd
-        x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           15       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
-           15       4     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
@@ -722,15 +486,13 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
             0       4     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddThree(int);
     descriptor: (I)I
@@ -749,277 +511,39 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.String unsupportedMethod();
     descriptor: ()Ljava/lang/String;
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-         x: ldc           #x                 // String unsupportedMethod
-         x: ldc           #x                 // String ()Ljava/lang/String;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
-        x: new           #x                 // class java/lang/RuntimeException
-        x: dup
-        x: ldc           #x                 // String Unreachable
-        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+      stack=3, locals=1, args_size=1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Unreachable
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
         x: athrow
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestThrow
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
 }
-SourceFile: "TinyFrameworkClassAnnotations.java"
+SourceFile: "TinyFrameworkAnnotations.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
-  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 3, methods: 8, attributes: 3
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int keep;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int remove;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=1, args_size=1
-         x: aload_0
-         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
-         x: aload_0
-         x: iconst_1
-         x: putfield      #x                 // Field stub:I
-         x: aload_0
-        x: iconst_2
-        x: putfield      #x                 // Field keep:I
-        x: return
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOne(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
-         x: aload_0
-         x: iload_1
-         x: invokevirtual #x                 // Method addOneInner:(I)I
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-            0       6     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
-         x: iload_1
-         x: iconst_1
-         x: iadd
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-            0       4     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public void toBeRemoved(java.lang.String);
-    descriptor: (Ljava/lang/String;)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
-         x: athrow
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-            0       8     1   foo   Ljava/lang/String;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=2, locals=2, args_size=2
-         x: iload_1
-         x: iconst_2
-         x: iadd
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-            0       4     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddThree(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=2, locals=1, args_size=1
-         x: iload_0
-         x: iconst_3
-         x: iadd
-         x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       4     0 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String unsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
-         x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
   Compiled from "TinyFrameworkClassLoadHook.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
@@ -1035,9 +559,7 @@
     Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
     descriptor: ()V
@@ -1053,9 +575,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static void onClassLoaded(java.lang.Class<?>);
     descriptor: (Ljava/lang/Class;)V
@@ -1077,9 +597,7 @@
     Signature: #x                          // (Ljava/lang/Class<*>;)V
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -1094,19 +612,115 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkClassLoadHook.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 4, attributes: 3
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iconst_1
+         x: putfield      #x                 // Field keep:I
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_1
+         x: iconst_2
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+            0       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Unreachable
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
   Compiled from "TinyFrameworkClassWithInitializerDefault.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
@@ -1121,35 +735,29 @@
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.lang.Object sObject;
     descriptor: Ljava/lang/Object;
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
 }
 SourceFile: "TinyFrameworkClassWithInitializerDefault.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.class
   Compiled from "TinyFrameworkClassWithInitializerStub.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub
@@ -1164,24 +772,20 @@
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.lang.Object sObject;
     descriptor: Ljava/lang/Object;
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   static {};
     descriptor: ()V
@@ -1201,21 +805,19 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkClassWithInitializerStub.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
@@ -1232,43 +834,37 @@
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private final java.lang.String mLongName;
     descriptor: Ljava/lang/String;
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
@@ -1278,7 +874,7 @@
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
@@ -1288,9 +884,7 @@
     flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
@@ -1304,9 +898,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
     descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
@@ -1324,9 +916,7 @@
             0      10     0  name   Ljava/lang/String;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      mandated
@@ -1356,12 +946,10 @@
     Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
     MethodParameters:
       Name                           Flags
       <no name>                      synthetic
@@ -1383,12 +971,10 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.lang.String getShortName();
     descriptor: ()Ljava/lang/String;
@@ -1404,12 +990,10 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
@@ -1434,9 +1018,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -1473,20 +1055,16 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
 SourceFile: "TinyFrameworkEnumComplex.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
   Compiled from "TinyFrameworkEnumSimple.java"
 public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
@@ -1501,33 +1079,27 @@
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
     descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
     flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -1541,9 +1113,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
     descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -1561,9 +1131,7 @@
             0      10     0  name   Ljava/lang/String;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      mandated
@@ -1585,9 +1153,7 @@
     Signature: #x                          // ()V
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      synthetic
@@ -1612,9 +1178,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -1639,20 +1203,16 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
 SourceFile: "TinyFrameworkEnumSimple.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
@@ -1676,9 +1236,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int testException();
     descriptor: ()I
@@ -1709,19 +1267,15 @@
            11      11     0     e   Ljava/lang/Exception;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkExceptionTester.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
   Compiled from "TinyFrameworkForTextPolicy.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
@@ -1730,22 +1284,13 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 17, attributes: 2
+  interfaces: 0, fields: 1, methods: 15, attributes: 2
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int keep;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
@@ -1753,7 +1298,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -1767,63 +1312,32 @@
          x: aload_0
          x: iconst_1
          x: putfield      #x                 // Field stub:I
-         x: aload_0
-        x: iconst_2
-        x: putfield      #x                 // Field keep:I
-        x: return
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int addOne(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=2, locals=2, args_size=2
-         x: aload_0
          x: iload_1
-         x: invokevirtual #x                 // Method addOneInner:(I)I
+         x: iconst_1
+         x: iadd
          x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
-            0       6     1 value   I
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       4     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                 // String addOneInner
-         x: ldc           #x                 // String (I)I
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iload_1
-        x: iconst_1
-        x: iadd
-        x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           15       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
-           15       4     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.String toBeIgnoredObj();
     descriptor: ()Ljava/lang/String;
@@ -1836,9 +1350,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public void toBeIgnoredV();
     descriptor: ()V
@@ -1850,9 +1362,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public boolean toBeIgnoredZ();
     descriptor: ()Z
@@ -1865,9 +1375,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public byte toBeIgnoredB();
     descriptor: ()B
@@ -1880,9 +1388,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public char toBeIgnoredC();
     descriptor: ()C
@@ -1895,9 +1401,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public short toBeIgnoredS();
     descriptor: ()S
@@ -1910,9 +1414,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int toBeIgnoredI();
     descriptor: ()I
@@ -1925,9 +1427,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public float toBeIgnoredF();
     descriptor: ()F
@@ -1940,9 +1440,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public double toBeIgnoredD();
     descriptor: ()D
@@ -1955,9 +1453,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int addTwo(int);
     descriptor: (I)I
@@ -1977,9 +1473,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddThree(int);
     descriptor: (I)I
@@ -1998,57 +1492,29 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.String unsupportedMethod();
     descriptor: ()Ljava/lang/String;
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                 // String unsupportedMethod
-         x: ldc           #x                 // String ()Ljava/lang/String;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
-        x: new           #x                 // class java/lang/RuntimeException
-        x: dup
-        x: ldc           #x                 // String Unreachable
-        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+      stack=3, locals=1, args_size=1
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Unreachable
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
         x: athrow
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=1, locals=1, args_size=1
-         x: aload_0
-         x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-         x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkForTextPolicy.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.class
   Compiled from "TinyFrameworkLambdas.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested
@@ -2064,12 +1530,10 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
     descriptor: Ljava/util/function/Supplier;
@@ -2077,12 +1541,10 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested();
     descriptor: ()V
@@ -2101,12 +1563,10 @@
             0      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.util.function.Supplier<java.lang.Integer> getSupplier();
     descriptor: ()Ljava/util/function/Supplier;
@@ -2122,12 +1582,10 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -2140,12 +1598,10 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static java.lang.Integer lambda$getSupplier_static$3();
     descriptor: ()Ljava/lang/Integer;
@@ -2158,9 +1614,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$getSupplier$2();
     descriptor: ()Ljava/lang/Integer;
@@ -2173,9 +1627,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$static$1();
     descriptor: ()Ljava/lang/Integer;
@@ -2188,9 +1640,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$new$0();
     descriptor: ()Ljava/lang/Integer;
@@ -2203,9 +1653,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -2218,7 +1666,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
@@ -2226,12 +1674,10 @@
 SourceFile: "TinyFrameworkLambdas.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 BootstrapMethods:
@@ -2271,12 +1717,10 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
     descriptor: Ljava/util/function/Supplier;
@@ -2284,12 +1728,10 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas();
     descriptor: ()V
@@ -2308,12 +1750,10 @@
             0      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.util.function.Supplier<java.lang.Integer> getSupplier();
     descriptor: ()Ljava/util/function/Supplier;
@@ -2329,12 +1769,10 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -2347,12 +1785,10 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static java.lang.Integer lambda$getSupplier_static$3();
     descriptor: ()Ljava/lang/Integer;
@@ -2365,9 +1801,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$getSupplier$2();
     descriptor: ()Ljava/lang/Integer;
@@ -2380,9 +1814,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$static$1();
     descriptor: ()Ljava/lang/Integer;
@@ -2395,9 +1827,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$new$0();
     descriptor: ()Ljava/lang/Integer;
@@ -2410,9 +1840,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -2425,7 +1853,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
@@ -2433,12 +1861,10 @@
 SourceFile: "TinyFrameworkLambdas.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 BootstrapMethods:
@@ -2487,9 +1913,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static void startThread(java.lang.Thread);
     descriptor: (Ljava/lang/Thread;)V
@@ -2508,9 +1932,7 @@
             0      10     0 thread   Ljava/lang/Thread;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int add(int, int);
     descriptor: (II)I
@@ -2528,18 +1950,14 @@
             0       4     1     b   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
-  public static #x= #x of #x;            // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static #x= #x of #x;             // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
 SourceFile: "TinyFrameworkMethodCallReplace.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
   Compiled from "TinyFrameworkMethodCallReplace.java"
@@ -2564,9 +1982,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
     descriptor: ()Z
@@ -2600,9 +2016,7 @@
       throws java.lang.Exception
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int staticMethodCallReplaceTester();
     descriptor: ()I
@@ -2616,9 +2030,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
     descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
@@ -2636,22 +2048,18 @@
             0      11     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
-  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static #x= #x of #x;            // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
   public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
 SourceFile: "TinyFrameworkMethodCallReplace.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 BootstrapMethods:
   x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
     Method arguments:
@@ -2668,15 +2076,13 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 11, attributes: 3
+  interfaces: 0, fields: 1, methods: 12, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
     descriptor: ()V
@@ -2692,9 +2098,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddTwo(int);
     descriptor: (I)I
@@ -2708,9 +2112,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddTwo_should_be_like_this(int);
     descriptor: (I)I
@@ -2726,9 +2128,7 @@
             0       5     0   arg   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static long nativeLongPlus(long, long);
     descriptor: (JJ)J
@@ -2743,9 +2143,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static long nativeLongPlus_should_be_like_this(long, long);
     descriptor: (JJ)J
@@ -2763,9 +2161,7 @@
             0       6     2  arg2   J
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public void setValue(int);
     descriptor: (I)V
@@ -2783,9 +2179,7 @@
             0       6     1     v   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int nativeNonStaticAddToValue(int);
     descriptor: (I)I
@@ -2800,9 +2194,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int nativeNonStaticAddToValue_should_be_like_this(int);
     descriptor: (I)I
@@ -2820,38 +2212,38 @@
             0       6     1   arg   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static void nativeStillNotSupported();
     descriptor: ()V
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
-      stack=4, locals=0, args_size=0
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
-         x: ldc           #x                 // String nativeStillNotSupported
-         x: ldc           #x                 // String ()V
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
-        x: new           #x                 // class java/lang/RuntimeException
-        x: dup
-        x: ldc           #x                 // String Unreachable
-        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+      stack=3, locals=0, args_size=0
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Unreachable
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
         x: athrow
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestThrow
 
+  public static native void nativeStillKeep();
+    descriptor: ()V
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
   public static void nativeStillNotSupported_should_be_like_this();
     descriptor: ()V
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
@@ -2864,9 +2256,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static byte nativeBytePlus(byte, byte);
     descriptor: (BB)B
@@ -2881,19 +2271,15 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
   x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
       value="TinyFrameworkNative_host"
@@ -2911,125 +2297,95 @@
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-         x: ldc           #x                 // String <init>
-         x: ldc           #x                 // String ()V
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: aload_0
-        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
-        x: return
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddTwo(int);
     descriptor: (I)I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-         x: ldc           #x                 // String nativeAddTwo
-         x: ldc           #x                 // String (I)I
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iload_0
-        x: iconst_2
-        x: iadd
-        x: ireturn
+      stack=2, locals=1, args_size=1
+         x: iload_0
+         x: iconst_2
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       4     0   arg   I
+            0       4     0   arg   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static long nativeLongPlus(long, long);
     descriptor: (JJ)J
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=4, args_size=2
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-         x: ldc           #x                 // String nativeLongPlus
-         x: ldc           #x                 // String (JJ)J
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: lload_0
-        x: lload_2
-        x: ladd
-        x: lreturn
+         x: lload_0
+         x: lload_2
+         x: ladd
+         x: lreturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       4     0  arg1   J
-           15       4     2  arg2   J
+            0       4     0  arg1   J
+            0       4     2  arg2   J
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int);
     descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-         x: ldc           #x                 // String nativeNonStaticAddToValue
-         x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: aload_0
-        x: getfield      #x                 // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I
-        x: iload_1
-        x: iadd
-        x: ireturn
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: getfield      #x                 // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I
+         x: iload_1
+         x: iadd
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
-           15       7     1   arg   I
+            0       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+            0       7     1   arg   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static byte nativeBytePlus(byte, byte);
     descriptor: (BB)B
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-         x: ldc           #x                 // String nativeBytePlus
-         x: ldc           #x                 // String (BB)B
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iload_0
-        x: iload_1
-        x: iadd
-        x: i2b
-        x: ireturn
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: i2b
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  arg1   B
-           15       5     1  arg2   B
+            0       5     0  arg1   B
+            0       5     1  arg2   B
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
     android.hosttest.annotation.HostSideTestWholeClassKeep
@@ -3047,7 +2403,7 @@
     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
     descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
@@ -3067,7 +2423,7 @@
             0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      final mandated
@@ -3076,45 +2432,33 @@
     descriptor: ()Ljava/lang/Integer;
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Integer;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iconst_1
-        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: iconst_1
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Object;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: aload_0
-        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
@@ -3123,7 +2467,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3148,51 +2492,39 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Integer;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iconst_2
-        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: iconst_2
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Object;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: aload_0
-        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
@@ -3201,7 +2533,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3217,7 +2549,7 @@
     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
     descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
@@ -3237,7 +2569,7 @@
             0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      final mandated
@@ -3246,45 +2578,33 @@
     descriptor: ()Ljava/lang/Integer;
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Integer;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iconst_3
-        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: iconst_3
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Object;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: aload_0
-        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
@@ -3293,7 +2613,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3318,51 +2638,39 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Integer;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iconst_4
-        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: iconst_4
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Object;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: aload_0
-        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
@@ -3371,7 +2679,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3387,9 +2695,7 @@
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int);
     descriptor: (I)V
@@ -3409,18 +2715,14 @@
             0      10     1     x   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
-  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;             // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3430,24 +2732,20 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 1, attributes: 5
+  interfaces: 0, fields: 2, methods: 1, attributes: 4
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
     descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
@@ -3470,24 +2768,17 @@
             0      15     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      final mandated
 }
 InnerClasses:
-  public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public #x= #x of #x;                    // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3512,51 +2803,39 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Integer;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: bipush        7
-        x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: bipush        7
+         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
     flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-         x: ldc           #x                 // String get
-         x: ldc           #x                 // String ()Ljava/lang/Object;
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: aload_0
-        x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
-        x: areturn
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
@@ -3566,7 +2845,50 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: bipush        8
+         x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3576,15 +2898,13 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 5
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass();
     descriptor: ()V
@@ -3603,9 +2923,7 @@
             0      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -3620,22 +2938,16 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
-  public static #x= #x of #x;            // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;             // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3662,19 +2974,15 @@
             0       6     1     x   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
-  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;             // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3691,9 +2999,7 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
     descriptor: Ljava/util/function/Supplier;
@@ -3701,9 +3007,7 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
     descriptor: ()V
@@ -3725,9 +3029,7 @@
             0      17     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.util.function.Supplier<java.lang.Integer> getSupplier();
     descriptor: ()Ljava/util/function/Supplier;
@@ -3746,9 +3048,7 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -3763,9 +3063,7 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -3780,33 +3078,31 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
@@ -3836,9 +3132,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int foo(int);
     descriptor: (I)I
@@ -3857,19 +3151,15 @@
             0      12     0 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkPackageRedirect.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
   Compiled from "TinyFrameworkRenamedClassCaller.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
@@ -3893,9 +3183,7 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int foo(int);
     descriptor: (I)I
@@ -3914,19 +3202,15 @@
             0      12     0 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkRenamedClassCaller.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
   Compiled from "A.java"
 public class com.android.hoststubgen.test.tinyframework.packagetest.A
@@ -3940,9 +3224,7 @@
 SourceFile: "A.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
   Compiled from "A.java"
 public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
@@ -3956,9 +3238,7 @@
 SourceFile: "A.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
   Compiled from "C1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
@@ -3972,9 +3252,7 @@
 SourceFile: "C1.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C2.class
   Compiled from "C2.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
@@ -3988,9 +3266,7 @@
 SourceFile: "C2.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C3.class
   Compiled from "C3.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
@@ -4004,9 +3280,7 @@
 SourceFile: "C3.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CA.class
   Compiled from "CA.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
@@ -4020,9 +3294,7 @@
 SourceFile: "CA.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CB.class
   Compiled from "CB.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
@@ -4036,9 +3308,7 @@
 SourceFile: "CB.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1.class
   Compiled from "Class_C1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
@@ -4052,7 +3322,7 @@
 SourceFile: "Class_C1.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2.class
   Compiled from "Class_C2.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
@@ -4066,7 +3336,7 @@
 SourceFile: "Class_C2.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3.class
   Compiled from "Class_C3.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C3
@@ -4080,7 +3350,7 @@
 SourceFile: "Class_C3.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1.class
   Compiled from "Class_I1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 implements com.android.hoststubgen.test.tinyframework.subclasstest.I1
@@ -4094,7 +3364,7 @@
 SourceFile: "Class_I1.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA.class
   Compiled from "Class_I1_IA.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA implements com.android.hoststubgen.test.tinyframework.subclasstest.I1,com.android.hoststubgen.test.tinyframework.subclasstest.IA
@@ -4108,7 +3378,7 @@
 SourceFile: "Class_I1_IA.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2.class
   Compiled from "Class_I2.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 implements com.android.hoststubgen.test.tinyframework.subclasstest.I2
@@ -4122,7 +3392,7 @@
 SourceFile: "Class_I2.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3.class
   Compiled from "Class_I3.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 implements com.android.hoststubgen.test.tinyframework.subclasstest.I3
@@ -4136,7 +3406,7 @@
 SourceFile: "Class_I3.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I1.class
   Compiled from "I1.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
@@ -4150,9 +3420,7 @@
 SourceFile: "I1.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
   Compiled from "I2.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 extends com.android.hoststubgen.test.tinyframework.subclasstest.I1
@@ -4166,9 +3434,7 @@
 SourceFile: "I2.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
   Compiled from "I3.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 extends com.android.hoststubgen.test.tinyframework.subclasstest.I2
@@ -4182,9 +3448,7 @@
 SourceFile: "I3.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
   Compiled from "IA.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
@@ -4198,9 +3462,7 @@
 SourceFile: "IA.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
   Compiled from "IB.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
@@ -4214,9 +3476,7 @@
 SourceFile: "IB.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/supported/UnsupportedClass.class
   Compiled from "UnsupportedClass.java"
 public class com.supported.UnsupportedClass
@@ -4231,60 +3491,48 @@
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public com.supported.UnsupportedClass(int);
     descriptor: (I)V
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                 // String com/supported/UnsupportedClass
-         x: ldc           #x                 // String <init>
-         x: ldc           #x                 // String (I)V
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: aload_0
-        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
-        x: aload_0
-        x: iload_1
-        x: putfield      #x                 // Field mValue:I
-        x: return
+      stack=2, locals=2, args_size=2
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: aload_0
+         x: iload_1
+         x: putfield      #x                 // Field mValue:I
+         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15      10     0  this   Lcom/supported/UnsupportedClass;
-           15      10     1 value   I
+            0      10     0  this   Lcom/supported/UnsupportedClass;
+            0      10     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int getValue();
     descriptor: ()I
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                 // String com/supported/UnsupportedClass
-         x: ldc           #x                 // String getValue
-         x: ldc           #x                 // String ()I
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: aload_0
-        x: getfield      #x                 // Field mValue:I
-        x: ireturn
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: getfield      #x                 // Field mValue:I
+         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           15       5     0  this   Lcom/supported/UnsupportedClass;
+            0       5     0  this   Lcom/supported/UnsupportedClass;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "UnsupportedClass.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
     android.hosttest.annotation.HostSideTestWholeClassKeep
@@ -4316,9 +3564,7 @@
             0      14     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int getValue();
     descriptor: ()I
@@ -4336,19 +3582,15 @@
             0      10     0  this   Lcom/unsupported/UnsupportedClass;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "UnsupportedClass.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
   Compiled from "TinyFrameworkToBeRenamed.java"
 public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
@@ -4363,9 +3605,7 @@
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
     descriptor: (I)V
@@ -4385,9 +3625,7 @@
             0      10     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int getValue();
     descriptor: ()I
@@ -4403,16 +3641,12 @@
             0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkToBeRenamed.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
deleted file mode 100644
index 1b83d24..0000000
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ /dev/null
@@ -1,2829 +0,0 @@
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy.class
-  Compiled from "IPretendingAidl.java"
-public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub$Proxy();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-SourceFile: "IPretendingAidl.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
-  Compiled from "IPretendingAidl.java"
-public class com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.IPretendingAidl$Stub();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int addOne(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-SourceFile: "IPretendingAidl.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
-  Compiled from "IPretendingAidl.java"
-public interface com.android.hoststubgen.test.tinyframework.IPretendingAidl
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 4
-}
-InnerClasses:
-  public static #x= #x of #x;            // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-  public static #x= #x of #x;           // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-SourceFile: "IPretendingAidl.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-  com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
-## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class
-  Compiled from "R.java"
-public class com.android.hoststubgen.test.tinyframework.R$Nested
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R$Nested
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 4
-  public static int[] ARRAY;
-    descriptor: [I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.R$Nested();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
-SourceFile: "R.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/R
-## Class: com/android/hoststubgen/test/tinyframework/R.class
-  Compiled from "R.java"
-public class com.android.hoststubgen.test.tinyframework.R
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/R
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 1, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.R();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
-SourceFile: "R.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/R$Nested
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
-  minor version: 0
-  major version: 61
-  flags: (0x0020) ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 4
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
-    descriptor: ()V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOneStub();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-}
-InnerClasses:
-  private static #x= #x of #x;           // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-SourceFile: "TinyFrameworkCallerCheck.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 5
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOne_withCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOne_noCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-SourceFile: "TinyFrameworkCallerCheck.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
-  Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 5, attributes: 3
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public int addOne(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddThree(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-}
-SourceFile: "TinyFrameworkClassAnnotations.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-  x: #x(#x=s#x)
-    android.hosttest.annotation.HostSideTestClassLoadHook(
-      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
-    )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
-  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 3, methods: 8, attributes: 3
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int keep;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int remove;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOne(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public void toBeRemoved(java.lang.String);
-    descriptor: (Ljava/lang/String;)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddThree(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String unsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
-  Compiled from "TinyFrameworkClassLoadHook.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 3, attributes: 3
-  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
-    descriptor: Ljava/util/Set;
-    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
-    descriptor: ()V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static void onClassLoaded(java.lang.Class<?>);
-    descriptor: (Ljava/lang/Class;)V
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // (Ljava/lang/Class<*>;)V
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassLoadHook.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
-  Compiled from "TinyFrameworkClassWithInitializerDefault.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 0, attributes: 3
-  public static boolean sInitialized;
-    descriptor: Z
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static java.lang.Object sObject;
-    descriptor: Ljava/lang/Object;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-}
-SourceFile: "TinyFrameworkClassWithInitializerDefault.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.class
-  Compiled from "TinyFrameworkClassWithInitializerStub.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 0, attributes: 3
-  public static boolean sInitialized;
-    descriptor: Z
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static java.lang.Object sObject;
-    descriptor: Ljava/lang/Object;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-}
-SourceFile: "TinyFrameworkClassWithInitializerStub.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x(#x=s#x)
-    android.hosttest.annotation.HostSideTestClassLoadHook(
-      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
-    )
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-  x: #x()
-    android.hosttest.annotation.HostSideTestStaticInitializerKeep
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
-  Compiled from "TinyFrameworkEnumComplex.java"
-public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex>
-  minor version: 0
-  major version: 61
-  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex
-  super_class: #x                         // java/lang/Enum
-  interfaces: 0, fields: 4, methods: 7, attributes: 4
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex RED;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $VALUES;
-    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
-    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
-    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    MethodParameters:
-      Name                           Flags
-      <no name>                      mandated
-
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
-    descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=3, locals=5, args_size=5
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-    MethodParameters:
-      Name                           Flags
-      <no name>                      synthetic
-      <no name>                      synthetic
-      <no name>
-      <no name>
-
-  public java.lang.String getLongName();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public java.lang.String getShortName();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
-    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
-SourceFile: "TinyFrameworkEnumComplex.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
-  Compiled from "TinyFrameworkEnumSimple.java"
-public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
-  minor version: 0
-  major version: 61
-  flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple
-  super_class: #x                         // java/lang/Enum
-  interfaces: 0, fields: 3, methods: 5, attributes: 4
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple CAT;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
-    descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
-    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
-    descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    MethodParameters:
-      Name                           Flags
-      <no name>                      mandated
-
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
-    descriptor: (Ljava/lang/String;I)V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=3, locals=3, args_size=3
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()V
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    MethodParameters:
-      Name                           Flags
-      <no name>                      synthetic
-      <no name>                      synthetic
-
-  private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
-    descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
-SourceFile: "TinyFrameworkEnumSimple.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
-  Compiled from "TinyFrameworkExceptionTester.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 3
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int testException();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkExceptionTester.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
-  Compiled from "TinyFrameworkForTextPolicy.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 14, attributes: 2
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOne(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String toBeIgnoredObj();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public void toBeIgnoredV();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public boolean toBeIgnoredZ();
-    descriptor: ()Z
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public byte toBeIgnoredB();
-    descriptor: ()B
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public char toBeIgnoredC();
-    descriptor: ()C
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public short toBeIgnoredS();
-    descriptor: ()S
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int toBeIgnoredI();
-    descriptor: ()I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public float toBeIgnoredF();
-    descriptor: ()F
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public double toBeIgnoredD();
-    descriptor: ()D
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddThree(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkForTextPolicy.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.class
-  Compiled from "TinyFrameworkLambdas.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 7, attributes: 5
-  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public java.util.function.Supplier<java.lang.Integer> getSupplier();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static java.lang.Integer lambda$getSupplier_static$3();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$getSupplier$2();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$static$1();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$new$0();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
-  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
-SourceFile: "TinyFrameworkLambdas.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-  x: #x()
-    android.hosttest.annotation.HostSideTestStaticInitializerKeep
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.class
-  Compiled from "TinyFrameworkLambdas.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 7, attributes: 5
-  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public java.util.function.Supplier<java.lang.Integer> getSupplier();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  private static java.lang.Integer lambda$getSupplier_static$3();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$getSupplier$2();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$static$1();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static java.lang.Integer lambda$new$0();
-    descriptor: ()Ljava/lang/Integer;
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
-  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
-SourceFile: "TinyFrameworkLambdas.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestStub
-  x: #x()
-    android.hosttest.annotation.HostSideTestStaticInitializerKeep
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
-  Compiled from "TinyFrameworkMethodCallReplace.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 3, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static void startThread(java.lang.Thread);
-    descriptor: (Ljava/lang/Thread;)V
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int add(int, int);
-    descriptor: (II)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
-SourceFile: "TinyFrameworkMethodCallReplace.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
-  Compiled from "TinyFrameworkMethodCallReplace.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 4, attributes: 5
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
-    descriptor: ()Z
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Exceptions:
-      throws java.lang.Exception
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int staticMethodCallReplaceTester();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
-    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
-    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
-  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
-SourceFile: "TinyFrameworkMethodCallReplace.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
-  Compiled from "TinyFrameworkNative.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 10, attributes: 3
-  int value;
-    descriptor: I
-    flags: (0x0000)
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static native int nativeAddTwo(int);
-    descriptor: (I)I
-    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddTwo_should_be_like_this(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static native long nativeLongPlus(long, long);
-    descriptor: (JJ)J
-    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static long nativeLongPlus_should_be_like_this(long, long);
-    descriptor: (JJ)J
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=4, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public void setValue(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public native int nativeNonStaticAddToValue(int);
-    descriptor: (I)I
-    flags: (0x0101) ACC_PUBLIC, ACC_NATIVE
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int nativeNonStaticAddToValue_should_be_like_this(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static void nativeStillNotSupported_should_be_like_this();
-    descriptor: ()V
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static native byte nativeBytePlus(byte, byte);
-    descriptor: (BB)B
-    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkNative.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-  x: #x(#x=s#x)
-    android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
-      value="TinyFrameworkNative_host"
-    )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 1, attributes: 4
-  public int value;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 1, attributes: 5
-  public int value;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
-    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
-    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
-    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    MethodParameters:
-      Name                           Flags
-      <no name>                      final mandated
-}
-InnerClasses:
-  public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 5
-  public int value;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
-  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
-  interfaces: 0, fields: 0, methods: 1, attributes: 4
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
-  Compiled from "TinyFrameworkNestedClasses.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 4, attributes: 5
-  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
-    descriptor: Ljava/util/function/Supplier;
-    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.util.function.Supplier<java.lang.Integer> getSupplier();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
-    descriptor: ()Ljava/util/function/Supplier;
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  static {};
-    descriptor: ()V
-    flags: (0x0008) ACC_STATIC
-    Code:
-      stack=3, locals=0, args_size=0
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
-  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-SourceFile: "TinyFrameworkNestedClasses.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.class
-  Compiled from "TinyFrameworkPackageRedirect.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 3
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkPackageRedirect();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int foo(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkPackageRedirect.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
-  Compiled from "TinyFrameworkRenamedClassCaller.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 3
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int foo(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkRenamedClassCaller.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
-  Compiled from "A.java"
-public class com.android.hoststubgen.test.tinyframework.packagetest.A
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/A
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "A.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
-  Compiled from "A.java"
-public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "A.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
-  Compiled from "C1.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C1
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "C1.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C2.class
-  Compiled from "C2.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C2
-  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C1
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "C2.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C3.class
-  Compiled from "C3.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/C3
-  super_class: #x                         // com/android/hoststubgen/test/tinyframework/subclasstest/C2
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "C3.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CA.class
-  Compiled from "CA.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CA
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "CA.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CB.class
-  Compiled from "CB.java"
-public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/CB
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "CB.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I1.class
-  Compiled from "I1.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I1
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "I1.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
-  Compiled from "I2.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 extends com.android.hoststubgen.test.tinyframework.subclasstest.I1
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I2
-  super_class: #x                         // java/lang/Object
-  interfaces: 1, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "I2.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
-  Compiled from "I3.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 extends com.android.hoststubgen.test.tinyframework.subclasstest.I2
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/I3
-  super_class: #x                         // java/lang/Object
-  interfaces: 1, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "I3.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
-  Compiled from "IA.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IA
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "IA.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
-  Compiled from "IB.java"
-public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
-  minor version: 0
-  major version: 61
-  flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/subclasstest/IB
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 0, attributes: 2
-}
-SourceFile: "IB.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-## Class: com/unsupported/UnsupportedClass.class
-  Compiled from "UnsupportedClass.java"
-public class com.unsupported.UnsupportedClass
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/unsupported/UnsupportedClass
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 2, attributes: 3
-  public com.unsupported.UnsupportedClass(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int getValue();
-    descriptor: ()I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "UnsupportedClass.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
-  Compiled from "TinyFrameworkToBeRenamed.java"
-public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 2, attributes: 3
-  private final int mValue;
-    descriptor: I
-    flags: (0x0012) ACC_PRIVATE, ACC_FINAL
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed(int);
-    descriptor: (I)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=2, args_size=2
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int getValue();
-    descriptor: ()I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=3, locals=1, args_size=1
-         x: new           #x                 // class java/lang/RuntimeException
-         x: dup
-         x: ldc           #x                 // String Stub!
-         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
-         x: athrow
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkToBeRenamed.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
similarity index 75%
rename from tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
rename to tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
index d12a23d..2ca723b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt
@@ -22,12 +22,12 @@
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "HostSideTestClassLoadHook.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
@@ -58,7 +58,7 @@
 SourceFile: "HostSideTestKeep.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
@@ -91,12 +91,12 @@
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "HostSideTestNativeSubstitutionClass.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
@@ -127,7 +127,7 @@
 SourceFile: "HostSideTestRemove.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
@@ -136,13 +136,13 @@
     java.lang.annotation.Retention(
       value=Ljava/lang/annotation/RetentionPolicy;.CLASS
     )
-## Class: android/hosttest/annotation/HostSideTestStub.class
-  Compiled from "HostSideTestStub.java"
-public interface android.hosttest.annotation.HostSideTestStub extends java.lang.annotation.Annotation
+## Class: android/hosttest/annotation/HostSideTestStaticInitializerKeep.class
+  Compiled from "HostSideTestStaticInitializerKeep.java"
+public interface android.hosttest.annotation.HostSideTestStaticInitializerKeep extends java.lang.annotation.Annotation
   minor version: 0
   major version: 61
   flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
-  this_class: #x                          // android/hosttest/annotation/HostSideTestStub
+  this_class: #x                          // android/hosttest/annotation/HostSideTestStaticInitializerKeep
   super_class: #x                         // java/lang/Object
   interfaces: 1, fields: 0, methods: 1, attributes: 2
   private static {};
@@ -150,15 +150,15 @@
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestStub
+         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestStaticInitializerKeep
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
-SourceFile: "HostSideTestStub.java"
+SourceFile: "HostSideTestStaticInitializerKeep.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
@@ -191,12 +191,12 @@
     flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "HostSideTestSubstitute.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.METHOD]
@@ -227,7 +227,7 @@
 SourceFile: "HostSideTestThrow.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x,e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
@@ -258,38 +258,7 @@
 SourceFile: "HostSideTestWholeClassKeep.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-  x: #x(#x=[e#x.#x])
-    java.lang.annotation.Target(
-      value=[Ljava/lang/annotation/ElementType;.TYPE]
-    )
-  x: #x(#x=e#x.#x)
-    java.lang.annotation.Retention(
-      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
-    )
-## Class: android/hosttest/annotation/HostSideTestWholeClassStub.class
-  Compiled from "HostSideTestWholeClassStub.java"
-public interface android.hosttest.annotation.HostSideTestWholeClassStub extends java.lang.annotation.Annotation
-  minor version: 0
-  major version: 61
-  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
-  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassStub
-  super_class: #x                         // java/lang/Object
-  interfaces: 1, fields: 0, methods: 1, attributes: 2
-  private static {};
-    descriptor: ()V
-    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
-    Code:
-      stack=2, locals=0, args_size=0
-         x: ldc           #x                  // class android/hosttest/annotation/HostSideTestWholeClassStub
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
-         x: return
-}
-SourceFile: "HostSideTestWholeClassStub.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
   x: #x(#x=[e#x.#x])
     java.lang.annotation.Target(
       value=[Ljava/lang/annotation/ElementType;.TYPE]
@@ -313,7 +282,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -336,9 +305,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int addTwo(int);
     descriptor: (I)I
@@ -360,9 +327,7 @@
            11       4     0     a   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;          // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
@@ -370,9 +335,7 @@
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
 ## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub.class
   Compiled from "IPretendingAidl.java"
@@ -389,7 +352,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -412,9 +375,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int addOne(int);
     descriptor: (I)I
@@ -436,9 +397,7 @@
            11       4     0     a   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl
@@ -446,9 +405,7 @@
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/IPretendingAidl
 ## Class: com/android/hoststubgen/test/tinyframework/IPretendingAidl.class
   Compiled from "IPretendingAidl.java"
@@ -465,7 +422,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/IPretendingAidl
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
@@ -475,9 +432,7 @@
 SourceFile: "IPretendingAidl.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub
   com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy
@@ -495,9 +450,7 @@
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public com.android.hoststubgen.test.tinyframework.R$Nested();
     descriptor: ()V
@@ -518,9 +471,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R$Nested;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -546,18 +497,14 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
-  public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
+  public static #x= #x of #x;             // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
 SourceFile: "R.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/R
 ## Class: com/android/hoststubgen/test/tinyframework/R.class
   Compiled from "R.java"
@@ -574,7 +521,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/R
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -597,239 +544,31 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/R;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R
 SourceFile: "R.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/R$Nested
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
-  minor version: 0
-  major version: 61
-  flags: (0x0020) ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 4, attributes: 4
-  private static {};
-    descriptor: ()V
-    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
-    Code:
-      stack=2, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
-         x: return
-
-  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
-    descriptor: ()V
-    flags: (0x0002) ACC_PRIVATE
-    Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-         x: ldc           #x                 // String <init>
-         x: ldc           #x                 // String ()V
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: aload_0
-        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
-        x: return
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOneKeep();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=4, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-         x: ldc           #x                 // String getOneKeep
-         x: ldc           #x                 // String ()I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-        x: ldc           #x                 // String getOneKeep
-        x: ldc           #x                 // String ()I
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: iconst_1
-        x: ireturn
-      LineNumberTable:
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestKeep
-
-  public static int getOneStub();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=4, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-         x: ldc           #x                 // String getOneStub
-         x: ldc           #x                 // String ()I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: iconst_1
-        x: ireturn
-      LineNumberTable:
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-}
-InnerClasses:
-  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-SourceFile: "TinyFrameworkCallerCheck.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
-  Compiled from "TinyFrameworkCallerCheck.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.class
+  Compiled from "TinyFrameworkAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations
   minor version: 0
   major version: 61
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 0, methods: 4, attributes: 5
-  private static {};
-    descriptor: ()V
-    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
-    Code:
-      stack=2, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
-         x: return
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-         x: ldc           #x                 // String <init>
-         x: ldc           #x                 // String ()V
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: aload_0
-        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
-        x: return
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOne_withCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=4, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-         x: ldc           #x                 // String getOne_withCheck
-         x: ldc           #x                 // String ()I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
-        x: ireturn
-      LineNumberTable:
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int getOne_noCheck();
-    descriptor: ()I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=4, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-         x: ldc           #x                 // String getOne_noCheck
-         x: ldc           #x                 // String ()I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I
-        x: ireturn
-      LineNumberTable:
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-InnerClasses:
-  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
-SourceFile: "TinyFrameworkCallerCheck.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
-NestMembers:
-  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
-  Compiled from "TinyFrameworkClassAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 8, attributes: 3
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
+  interfaces: 0, fields: 1, methods: 6, attributes: 3
   public int keep;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
@@ -839,20 +578,20 @@
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
          x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
         x: return
 
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkAnnotations();
     descriptor: ()V
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
          x: ldc           #x                 // String <init>
          x: ldc           #x                 // String ()V
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -861,68 +600,29 @@
         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
         x: aload_0
         x: iconst_1
-        x: putfield      #x                 // Field stub:I
-        x: aload_0
-        x: iconst_2
         x: putfield      #x                 // Field keep:I
         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           11      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public int addOne(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=2, args_size=2
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
          x: ldc           #x                 // String addOne
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: aload_0
-        x: iload_1
-        x: invokevirtual #x                 // Method addOneInner:(I)I
-        x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
-           11       6     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-         x: ldc           #x                 // String addOneInner
-         x: ldc           #x                 // String (I)I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-        x: ldc           #x                 // String addOneInner
-        x: ldc           #x                 // String (I)I
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: iload_1
         x: iconst_1
         x: iadd
@@ -930,11 +630,11 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
-           26       4     1 value   I
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
+           11       4     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
@@ -944,7 +644,7 @@
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=2, args_size=2
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
          x: ldc           #x                 // String addTwo
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -956,22 +656,20 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations;
            11       4     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddThree(int);
     descriptor: (I)I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
          x: ldc           #x                 // String nativeAddThree
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -988,26 +686,18 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.String unsupportedMethod();
     descriptor: ()Ljava/lang/String;
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations
          x: ldc           #x                 // String unsupportedMethod
          x: ldc           #x                 // String ()Ljava/lang/String;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-        x: ldc           #x                 // String unsupportedMethod
-        x: ldc           #x                 // String ()Ljava/lang/String;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
         x: new           #x                 // class java/lang/RuntimeException
         x: dup
@@ -1018,307 +708,22 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestThrow
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
-         x: ldc           #x                 // String visibleButUsesUnsupportedMethod
-         x: ldc           #x                 // String ()Ljava/lang/String;
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: aload_0
-        x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-        x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-    RuntimeInvisibleAnnotations:
-      x: #x()
-        android.hosttest.annotation.HostSideTestStub
 }
-SourceFile: "TinyFrameworkClassAnnotations.java"
+SourceFile: "TinyFrameworkAnnotations.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
-## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
-  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
-public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
-  minor version: 0
-  major version: 61
-  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
-  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-  super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 3, methods: 9, attributes: 3
-  public int stub;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int keep;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int remove;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  private static {};
-    descriptor: ()V
-    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
-    Code:
-      stack=2, locals=0, args_size=0
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
-         x: return
-
-  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
-    descriptor: ()V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-         x: ldc           #x                 // String <init>
-         x: ldc           #x                 // String ()V
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: aload_0
-        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
-        x: aload_0
-        x: iconst_1
-        x: putfield      #x                 // Field stub:I
-        x: aload_0
-        x: iconst_2
-        x: putfield      #x                 // Field keep:I
-        x: return
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOne(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-         x: ldc           #x                 // String addOne
-         x: ldc           #x                 // String (I)I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: aload_0
-        x: iload_1
-        x: invokevirtual #x                 // Method addOneInner:(I)I
-        x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-           11       6     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-         x: ldc           #x                 // String addOneInner
-         x: ldc           #x                 // String (I)I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: iload_1
-        x: iconst_1
-        x: iadd
-        x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-           11       4     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public void toBeRemoved(java.lang.String);
-    descriptor: (Ljava/lang/String;)V
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-         x: ldc           #x                 // String toBeRemoved
-         x: ldc           #x                 // String (Ljava/lang/String;)V
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: new           #x                 // class java/lang/RuntimeException
-        x: dup
-        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
-        x: athrow
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-           11       8     1   foo   Ljava/lang/String;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addTwo(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-         x: ldc           #x                 // String addTwo
-         x: ldc           #x                 // String (I)I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: iload_1
-        x: iconst_2
-        x: iadd
-        x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-           11       4     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public static int nativeAddThree(int);
-    descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
-    Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-         x: ldc           #x                 // String nativeAddThree
-         x: ldc           #x                 // String (I)I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: iload_0
-        x: iconst_3
-        x: iadd
-        x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       4     0 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String unsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-         x: ldc           #x                 // String unsupportedMethod
-         x: ldc           #x                 // String ()Ljava/lang/String;
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String This value shouldn\'t be seen on the host side.
-        x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
-         x: ldc           #x                 // String visibleButUsesUnsupportedMethod
-         x: ldc           #x                 // String ()Ljava/lang/String;
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: aload_0
-        x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
-        x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-}
-SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
-RuntimeVisibleAnnotations:
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
   Compiled from "TinyFrameworkClassLoadHook.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
@@ -1334,9 +739,7 @@
     Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
     descriptor: ()V
@@ -1357,9 +760,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static void onClassLoaded(java.lang.Class<?>);
     descriptor: (Ljava/lang/Class;)V
@@ -1386,9 +787,7 @@
     Signature: #x                          // (Ljava/lang/Class<*>;)V
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -1411,19 +810,145 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkClassLoadHook.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 5, attributes: 3
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: aload_0
+        x: iconst_1
+        x: putfield      #x                 // Field keep:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+         x: ldc           #x                 // String addOne
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_1
+        x: iconst_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+           11       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+         x: ldc           #x                 // String addTwo
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_1
+        x: iconst_2
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations;
+           11       4     1 value   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations
+         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+}
+SourceFile: "TinyFrameworkClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.class
   Compiled from "TinyFrameworkClassWithInitializerDefault.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerDefault
@@ -1438,35 +963,29 @@
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.lang.Object sObject;
     descriptor: Ljava/lang/Object;
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
 }
 SourceFile: "TinyFrameworkClassWithInitializerDefault.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.class
   Compiled from "TinyFrameworkClassWithInitializerStub.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializerStub
@@ -1481,24 +1000,20 @@
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.lang.Object sObject;
     descriptor: Ljava/lang/Object;
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   static {};
     descriptor: ()V
@@ -1526,21 +1041,19 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkClassWithInitializerStub.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestClassLoadHook(
       value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
     )
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.class
@@ -1557,43 +1070,37 @@
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex GREEN;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex BLUE;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private final java.lang.String mLongName;
     descriptor: Ljava/lang/String;
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
@@ -1603,7 +1110,7 @@
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestKeep
@@ -1613,9 +1120,7 @@
     flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
@@ -1634,9 +1139,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex valueOf(java.lang.String);
     descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
@@ -1659,9 +1162,7 @@
            11      10     0  name   Ljava/lang/String;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      mandated
@@ -1696,12 +1197,10 @@
     Signature: #x                          // (Ljava/lang/String;Ljava/lang/String;)V
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
     MethodParameters:
       Name                           Flags
       <no name>                      synthetic
@@ -1728,12 +1227,10 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.lang.String getShortName();
     descriptor: ()Ljava/lang/String;
@@ -1754,12 +1251,10 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex[] $values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
@@ -1789,9 +1284,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -1826,7 +1319,7 @@
         x: dup
         x: ldc           #x                 // String BLUE
         x: iconst_2
-        x: ldc           #x                // String Blue
+        x: ldc           #x                 // String Blue
         x: ldc           #x                // String B
         x: invokespecial #x                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
         x: putstatic     #x                 // Field BLUE:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
@@ -1836,20 +1329,16 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;>;
 SourceFile: "TinyFrameworkEnumComplex.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.class
   Compiled from "TinyFrameworkEnumSimple.java"
 public final class com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple extends java.lang.Enum<com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple>
@@ -1864,33 +1353,27 @@
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple DOG;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
     flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static final com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $VALUES;
     descriptor: [Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
     flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] values();
     descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -1909,9 +1392,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple valueOf(java.lang.String);
     descriptor: (Ljava/lang/String;)Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -1934,9 +1415,7 @@
            11      10     0  name   Ljava/lang/String;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      mandated
@@ -1963,9 +1442,7 @@
     Signature: #x                          // ()V
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      synthetic
@@ -1995,9 +1472,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -2030,20 +1505,16 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 Signature: #x                           // Ljava/lang/Enum<Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;>;
 SourceFile: "TinyFrameworkEnumSimple.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
   Compiled from "TinyFrameworkExceptionTester.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
@@ -2059,7 +1530,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -2082,9 +1553,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int testException();
     descriptor: ()I
@@ -2120,19 +1589,15 @@
            22      11     0     e   Ljava/lang/Exception;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkExceptionTester.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
   Compiled from "TinyFrameworkForTextPolicy.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
@@ -2141,22 +1606,13 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 17, attributes: 2
+  interfaces: 0, fields: 1, methods: 15, attributes: 2
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int keep;
-    descriptor: I
-    flags: (0x0001) ACC_PUBLIC
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
@@ -2164,7 +1620,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
          x: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
@@ -2186,19 +1642,14 @@
         x: aload_0
         x: iconst_1
         x: putfield      #x                 // Field stub:I
-        x: aload_0
-        x: iconst_2
-        x: putfield      #x                 // Field keep:I
         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           11      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+           11      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int addOne(int);
     descriptor: (I)I
@@ -2210,37 +1661,6 @@
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: aload_0
-        x: iload_1
-        x: invokevirtual #x                 // Method addOneInner:(I)I
-        x: ireturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
-           11       6     1 value   I
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public int addOneInner(int);
-    descriptor: (I)I
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=2, args_size=2
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                 // String addOneInner
-         x: ldc           #x                 // String (I)I
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-        x: ldc           #x                 // String addOneInner
-        x: ldc           #x                 // String (I)I
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: iload_1
         x: iconst_1
         x: iadd
@@ -2248,11 +1668,11 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
-           26       4     1 value   I
+           11       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+           11       4     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.String toBeIgnoredObj();
     descriptor: ()Ljava/lang/String;
@@ -2270,9 +1690,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public void toBeIgnoredV();
     descriptor: ()V
@@ -2289,9 +1707,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public boolean toBeIgnoredZ();
     descriptor: ()Z
@@ -2309,9 +1725,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public byte toBeIgnoredB();
     descriptor: ()B
@@ -2329,9 +1743,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public char toBeIgnoredC();
     descriptor: ()C
@@ -2349,9 +1761,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public short toBeIgnoredS();
     descriptor: ()S
@@ -2369,9 +1779,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int toBeIgnoredI();
     descriptor: ()I
@@ -2389,9 +1797,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public float toBeIgnoredF();
     descriptor: ()F
@@ -2409,9 +1815,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public double toBeIgnoredD();
     descriptor: ()D
@@ -2429,9 +1833,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int addTwo(int);
     descriptor: (I)I
@@ -2439,7 +1841,7 @@
     Code:
       stack=4, locals=2, args_size=2
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                // String addTwo
+         x: ldc           #x                 // String addTwo
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
@@ -2456,9 +1858,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddThree(int);
     descriptor: (I)I
@@ -2466,7 +1866,7 @@
     Code:
       stack=4, locals=1, args_size=1
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                // String nativeAddThree
+         x: ldc           #x                 // String nativeAddThree
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
@@ -2482,9 +1882,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.String unsupportedMethod();
     descriptor: ()Ljava/lang/String;
@@ -2492,57 +1890,26 @@
     Code:
       stack=4, locals=1, args_size=1
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                // String unsupportedMethod
+         x: ldc           #x                 // String unsupportedMethod
          x: ldc           #x                 // String ()Ljava/lang/String;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-        x: ldc           #x                // String unsupportedMethod
-        x: ldc           #x                 // String ()Ljava/lang/String;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: invokestatic  #x                // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
-        x: new           #x                // class java/lang/RuntimeException
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
         x: dup
-        x: ldc           #x                // String Unreachable
-        x: invokespecial #x                // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
         x: athrow
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-
-  public java.lang.String visibleButUsesUnsupportedMethod();
-    descriptor: ()Ljava/lang/String;
-    flags: (0x0001) ACC_PUBLIC
-    Code:
-      stack=4, locals=1, args_size=1
-         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                // String visibleButUsesUnsupportedMethod
-         x: ldc           #x                 // String ()Ljava/lang/String;
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
-         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: aload_0
-        x: invokevirtual #x                // Method unsupportedMethod:()Ljava/lang/String;
-        x: areturn
-      LineNumberTable:
-      LocalVariableTable:
-        Start  Length  Slot  Name   Signature
-           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
-    RuntimeVisibleAnnotations:
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkForTextPolicy.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested.class
   Compiled from "TinyFrameworkLambdas.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested
@@ -2558,12 +1925,10 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
     descriptor: Ljava/util/function/Supplier;
@@ -2571,12 +1936,10 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas$Nested();
     descriptor: ()V
@@ -2600,12 +1963,10 @@
            11      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.util.function.Supplier<java.lang.Integer> getSupplier();
     descriptor: ()Ljava/util/function/Supplier;
@@ -2626,12 +1987,10 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -2649,12 +2008,10 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static java.lang.Integer lambda$getSupplier_static$3();
     descriptor: ()Ljava/lang/Integer;
@@ -2672,9 +2029,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$getSupplier$2();
     descriptor: ()Ljava/lang/Integer;
@@ -2692,9 +2047,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$static$1();
     descriptor: ()Ljava/lang/Integer;
@@ -2712,9 +2065,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$new$0();
     descriptor: ()Ljava/lang/Integer;
@@ -2732,9 +2083,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -2750,12 +2099,12 @@
         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
-        x: putstatic     #x                // Field sSupplier:Ljava/util/function/Supplier;
+        x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
         x: return
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;            // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
@@ -2763,12 +2112,10 @@
 SourceFile: "TinyFrameworkLambdas.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 BootstrapMethods:
@@ -2808,12 +2155,10 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
     descriptor: Ljava/util/function/Supplier;
@@ -2821,12 +2166,10 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkLambdas();
     descriptor: ()V
@@ -2850,12 +2193,10 @@
            11      14     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public java.util.function.Supplier<java.lang.Integer> getSupplier();
     descriptor: ()Ljava/util/function/Supplier;
@@ -2876,12 +2217,10 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -2899,12 +2238,10 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
-        android.hosttest.annotation.HostSideTestStub
+        android.hosttest.annotation.HostSideTestKeep
 
   private static java.lang.Integer lambda$getSupplier_static$3();
     descriptor: ()Ljava/lang/Integer;
@@ -2922,9 +2259,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$getSupplier$2();
     descriptor: ()Ljava/lang/Integer;
@@ -2942,9 +2277,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$static$1();
     descriptor: ()Ljava/lang/Integer;
@@ -2962,9 +2295,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static java.lang.Integer lambda$new$0();
     descriptor: ()Ljava/lang/Integer;
@@ -2982,9 +2313,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -3000,12 +2329,12 @@
         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
         x: invokedynamic #x,  0             // InvokeDynamic #x:get:()Ljava/util/function/Supplier;
-        x: putstatic     #x                // Field sSupplier:Ljava/util/function/Supplier;
+        x: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
         x: return
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
@@ -3013,12 +2342,10 @@
 SourceFile: "TinyFrameworkLambdas.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestStub
+    android.hosttest.annotation.HostSideTestKeep
   x: #x()
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 BootstrapMethods:
@@ -3059,7 +2386,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -3082,9 +2409,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static void startThread(java.lang.Thread);
     descriptor: (Ljava/lang/Thread;)V
@@ -3108,9 +2433,7 @@
            11      10     0 thread   Ljava/lang/Thread;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int add(int, int);
     descriptor: (II)I
@@ -3133,18 +2456,14 @@
            11       4     1     b   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
 SourceFile: "TinyFrameworkMethodCallReplace.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
   Compiled from "TinyFrameworkMethodCallReplace.java"
@@ -3161,7 +2480,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -3184,9 +2503,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
     descriptor: ()Z
@@ -3225,9 +2542,7 @@
       throws java.lang.Exception
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int staticMethodCallReplaceTester();
     descriptor: ()I
@@ -3246,9 +2561,7 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
     descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
@@ -3261,7 +2574,7 @@
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
         x: aload_0
-        x: invokestatic  #x                // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
+        x: invokestatic  #x                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
         x: invokevirtual #x                // Method java/lang/Thread.isDaemon:()Z
         x: invokevirtual #x                // Method java/util/concurrent/atomic/AtomicBoolean.set:(Z)V
         x: return
@@ -3271,9 +2584,7 @@
            11      11     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
@@ -3281,12 +2592,10 @@
 SourceFile: "TinyFrameworkMethodCallReplace.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 BootstrapMethods:
   x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
     Method arguments:
@@ -3303,15 +2612,13 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 12, attributes: 3
+  interfaces: 0, fields: 1, methods: 13, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
@@ -3319,7 +2626,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -3342,9 +2649,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddTwo(int);
     descriptor: (I)I
@@ -3363,9 +2668,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddTwo_should_be_like_this(int);
     descriptor: (I)I
@@ -3386,9 +2689,7 @@
            11       5     0   arg   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static long nativeLongPlus(long, long);
     descriptor: (JJ)J
@@ -3408,9 +2709,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static long nativeLongPlus_should_be_like_this(long, long);
     descriptor: (JJ)J
@@ -3433,9 +2732,7 @@
            11       6     2  arg2   J
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public void setValue(int);
     descriptor: (I)V
@@ -3458,9 +2755,7 @@
            11       6     1     v   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int nativeNonStaticAddToValue(int);
     descriptor: (I)I
@@ -3480,9 +2775,7 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int nativeNonStaticAddToValue_should_be_like_this(int);
     descriptor: (I)I
@@ -3505,9 +2798,7 @@
            11       6     1   arg   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static void nativeStillNotSupported();
     descriptor: ()V
@@ -3519,49 +2810,49 @@
          x: ldc           #x                 // String ()V
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
-        x: ldc           #x                 // String nativeStillNotSupported
-        x: ldc           #x                 // String ()V
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
         x: new           #x                 // class java/lang/RuntimeException
         x: dup
         x: ldc           #x                 // String Unreachable
-        x: invokespecial #x                // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
         x: athrow
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     RuntimeInvisibleAnnotations:
       x: #x()
         android.hosttest.annotation.HostSideTestThrow
 
+  public static native void nativeStillKeep();
+    descriptor: ()V
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
   public static void nativeStillNotSupported_should_be_like_this();
     descriptor: ()V
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
-         x: ldc           #x                // String nativeStillNotSupported_should_be_like_this
+         x: ldc           #x                 // String nativeStillNotSupported_should_be_like_this
          x: ldc           #x                 // String ()V
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
         x: new           #x                 // class java/lang/RuntimeException
         x: dup
-        x: invokespecial #x                // Method java/lang/RuntimeException."<init>":()V
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
         x: athrow
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static byte nativeBytePlus(byte, byte);
     descriptor: (BB)B
@@ -3569,31 +2860,27 @@
     Code:
       stack=4, locals=2, args_size=2
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
-         x: ldc           #x                // String nativeBytePlus
-         x: ldc           #x                // String (BB)B
+         x: ldc           #x                 // String nativeBytePlus
+         x: ldc           #x                 // String (BB)B
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
         x: iload_0
         x: iload_1
-        x: invokestatic  #x                // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B
         x: ireturn
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
   x: #x(#x=s#x)
     android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
       value="TinyFrameworkNative_host"
@@ -3627,22 +2914,16 @@
          x: ldc           #x                 // String ()V
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-        x: ldc           #x                 // String <init>
-        x: ldc           #x                 // String ()V
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: aload_0
         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
         x: return
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeAddTwo(int);
     descriptor: (I)I
@@ -3654,12 +2935,6 @@
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-        x: ldc           #x                 // String nativeAddTwo
-        x: ldc           #x                 // String (I)I
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: iload_0
         x: iconst_2
         x: iadd
@@ -3667,10 +2942,10 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       4     0   arg   I
+           11       4     0   arg   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static long nativeLongPlus(long, long);
     descriptor: (JJ)J
@@ -3682,12 +2957,6 @@
          x: ldc           #x                 // String (JJ)J
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-        x: ldc           #x                 // String nativeLongPlus
-        x: ldc           #x                 // String (JJ)J
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: lload_0
         x: lload_2
         x: ladd
@@ -3695,11 +2964,11 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       4     0  arg1   J
-           26       4     2  arg2   J
+           11       4     0  arg1   J
+           11       4     2  arg2   J
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int nativeNonStaticAddToValue(com.android.hoststubgen.test.tinyframework.TinyFrameworkNative, int);
     descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
@@ -3711,12 +2980,6 @@
          x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-        x: ldc           #x                 // String nativeNonStaticAddToValue
-        x: ldc           #x                 // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: aload_0
         x: getfield      #x                 // Field com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.value:I
         x: iload_1
@@ -3725,11 +2988,11 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
-           26       7     1   arg   I
+           11       7     0 source   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+           11       7     1   arg   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static byte nativeBytePlus(byte, byte);
     descriptor: (BB)B
@@ -3741,12 +3004,6 @@
          x: ldc           #x                 // String (BB)B
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
-        x: ldc           #x                 // String nativeBytePlus
-        x: ldc           #x                 // String (BB)B
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: iload_0
         x: iload_1
         x: iadd
@@ -3755,16 +3012,16 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  arg1   B
-           26       5     1  arg2   B
+           11       5     0  arg1   B
+           11       5     1  arg2   B
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkNative_host.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
     android.hosttest.annotation.HostSideTestWholeClassKeep
@@ -3782,7 +3039,7 @@
     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
@@ -3817,7 +3074,7 @@
            11      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      final mandated
@@ -3832,22 +3089,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Integer;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Integer;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: iconst_1
         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
@@ -3859,22 +3110,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Object;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Object;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: aload_0
         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
@@ -3883,7 +3128,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -3923,7 +3168,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
@@ -3935,22 +3180,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Integer;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Integer;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: iconst_2
         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
@@ -3962,22 +3201,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Object;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Object;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: aload_0
         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
@@ -3986,7 +3219,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -4002,7 +3235,7 @@
     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
@@ -4037,7 +3270,7 @@
            11      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      final mandated
@@ -4052,22 +3285,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Integer;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Integer;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: iconst_3
         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
@@ -4079,22 +3306,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Object;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Object;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: aload_0
         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
@@ -4103,7 +3324,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -4143,7 +3364,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
@@ -4155,22 +3376,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Integer;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Integer;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: iconst_4
         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
@@ -4182,22 +3397,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Object;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Object;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: aload_0
         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
@@ -4206,7 +3415,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -4222,9 +3431,7 @@
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
@@ -4232,7 +3439,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -4259,18 +3466,14 @@
            11      10     1     x   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -4280,24 +3483,20 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 2, attributes: 5
+  interfaces: 0, fields: 2, methods: 2, attributes: 4
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
     descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
@@ -4305,7 +3504,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -4335,9 +3534,7 @@
            11      15     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
     MethodParameters:
       Name                           Flags
       <no name>                      final mandated
@@ -4347,12 +3544,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -4392,7 +3584,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Integer get();
     descriptor: ()Ljava/lang/Integer;
@@ -4404,22 +3596,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Integer;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Integer;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: bipush        7
         x: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+           11       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.lang.Object get();
     descriptor: ()Ljava/lang/Object;
@@ -4431,22 +3617,16 @@
          x: ldc           #x                 // String ()Ljava/lang/Object;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
-        x: ldc           #x                 // String get
-        x: ldc           #x                 // String ()Ljava/lang/Object;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: aload_0
         x: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
@@ -4456,7 +3636,65 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: aload_0
+        x: bipush        8
+        x: putfield      #x                 // Field value:I
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
+}
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -4466,15 +3704,13 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 3, attributes: 5
+  interfaces: 0, fields: 1, methods: 3, attributes: 4
   public int value;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
@@ -4482,7 +3718,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -4508,9 +3744,7 @@
            11      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -4530,22 +3764,16 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  public static #x= #x of #x;           // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
-RuntimeInvisibleAnnotations:
-  x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -4562,7 +3790,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -4587,9 +3815,7 @@
            11       6     1     x   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
@@ -4597,9 +3823,7 @@
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
   Compiled from "TinyFrameworkNestedClasses.java"
@@ -4616,9 +3840,7 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
     descriptor: Ljava/util/function/Supplier;
@@ -4626,9 +3848,7 @@
     Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
     descriptor: ()V
@@ -4655,9 +3875,7 @@
            11      17     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public java.util.function.Supplier<java.lang.Integer> getSupplier();
     descriptor: ()Ljava/util/function/Supplier;
@@ -4681,9 +3899,7 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
     descriptor: ()Ljava/util/function/Supplier;
@@ -4703,9 +3919,7 @@
     Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   static {};
     descriptor: ()V
@@ -4728,33 +3942,31 @@
       LineNumberTable:
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 InnerClasses:
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
-  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
   public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // Double$NestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
   #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
 SourceFile: "TinyFrameworkNestedClasses.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$Double$NestedClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
   com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
@@ -4776,7 +3988,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -4799,9 +4011,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int foo(int);
     descriptor: (I)I
@@ -4825,19 +4035,15 @@
            11      12     0 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkPackageRedirect.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.class
   Compiled from "TinyFrameworkRenamedClassCaller.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkRenamedClassCaller
@@ -4853,7 +4059,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -4876,9 +4082,7 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public static int foo(int);
     descriptor: (I)I
@@ -4902,19 +4106,15 @@
            11      12     0 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkRenamedClassCaller.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
   Compiled from "A.java"
 public class com.android.hoststubgen.test.tinyframework.packagetest.A
@@ -4930,16 +4130,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/packagetest/A
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "A.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
   Compiled from "A.java"
 public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
@@ -4955,16 +4153,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/packagetest/sub/A
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "A.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
   Compiled from "C1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
@@ -4980,16 +4176,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/subclasstest/C1
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "C1.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C2.class
   Compiled from "C2.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
@@ -5005,16 +4199,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/subclasstest/C2
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "C2.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C3.class
   Compiled from "C3.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
@@ -5030,16 +4222,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/subclasstest/C3
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "C3.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CA.class
   Compiled from "CA.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.CA
@@ -5055,16 +4245,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/subclasstest/CA
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "CA.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/CB.class
   Compiled from "CB.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.CB
@@ -5080,16 +4268,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/subclasstest/CB
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "CB.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1.class
   Compiled from "Class_C1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C1 extends com.android.hoststubgen.test.tinyframework.subclasstest.C1
@@ -5112,7 +4298,7 @@
 SourceFile: "Class_C1.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2.class
   Compiled from "Class_C2.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C2 extends com.android.hoststubgen.test.tinyframework.subclasstest.C2
@@ -5135,7 +4321,7 @@
 SourceFile: "Class_C2.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3.class
   Compiled from "Class_C3.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_C3 extends com.android.hoststubgen.test.tinyframework.subclasstest.C3
@@ -5158,7 +4344,7 @@
 SourceFile: "Class_C3.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1.class
   Compiled from "Class_I1.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1 implements com.android.hoststubgen.test.tinyframework.subclasstest.I1
@@ -5181,7 +4367,7 @@
 SourceFile: "Class_I1.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA.class
   Compiled from "Class_I1_IA.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I1_IA implements com.android.hoststubgen.test.tinyframework.subclasstest.I1,com.android.hoststubgen.test.tinyframework.subclasstest.IA
@@ -5204,7 +4390,7 @@
 SourceFile: "Class_I1_IA.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2.class
   Compiled from "Class_I2.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I2 implements com.android.hoststubgen.test.tinyframework.subclasstest.I2
@@ -5227,7 +4413,7 @@
 SourceFile: "Class_I2.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3.class
   Compiled from "Class_I3.java"
 public class com.android.hoststubgen.test.tinyframework.subclasstest.Class_I3 implements com.android.hoststubgen.test.tinyframework.subclasstest.I3
@@ -5250,7 +4436,7 @@
 SourceFile: "Class_I3.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I1.class
   Compiled from "I1.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.I1
@@ -5266,16 +4452,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/subclasstest/I1
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "I1.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I2.class
   Compiled from "I2.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.I2 extends com.android.hoststubgen.test.tinyframework.subclasstest.I1
@@ -5298,9 +4482,7 @@
 SourceFile: "I2.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/I3.class
   Compiled from "I3.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.I3 extends com.android.hoststubgen.test.tinyframework.subclasstest.I2
@@ -5323,9 +4505,7 @@
 SourceFile: "I3.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IA.class
   Compiled from "IA.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.IA
@@ -5341,16 +4521,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/subclasstest/IA
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "IA.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/android/hoststubgen/test/tinyframework/subclasstest/IB.class
   Compiled from "IB.java"
 public interface com.android.hoststubgen.test.tinyframework.subclasstest.IB
@@ -5366,16 +4544,14 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/subclasstest/IB
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 }
 SourceFile: "IB.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 ## Class: com/supported/UnsupportedClass.class
   Compiled from "UnsupportedClass.java"
 public class com.supported.UnsupportedClass
@@ -5390,7 +4566,7 @@
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
@@ -5412,12 +4588,6 @@
          x: ldc           #x                 // String (I)V
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/supported/UnsupportedClass
-        x: ldc           #x                 // String <init>
-        x: ldc           #x                 // String (I)V
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: aload_0
         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
         x: aload_0
@@ -5427,11 +4597,11 @@
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26      10     0  this   Lcom/supported/UnsupportedClass;
-           26      10     1 value   I
+           11      10     0  this   Lcom/supported/UnsupportedClass;
+           11      10     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int getValue();
     descriptor: ()I
@@ -5443,27 +4613,21 @@
          x: ldc           #x                 // String ()I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-        x: ldc           #x                 // String com/supported/UnsupportedClass
-        x: ldc           #x                 // String getValue
-        x: ldc           #x                 // String ()I
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
-        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
         x: aload_0
         x: getfield      #x                 // Field mValue:I
         x: ireturn
       LineNumberTable:
       LocalVariableTable:
         Start  Length  Slot  Name   Signature
-           26       5     0  this   Lcom/supported/UnsupportedClass;
+           11       5     0  this   Lcom/supported/UnsupportedClass;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "UnsupportedClass.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
     android.hosttest.annotation.HostSideTestWholeClassKeep
@@ -5482,7 +4646,7 @@
     Code:
       stack=2, locals=0, args_size=0
          x: ldc           #x                  // class com/unsupported/UnsupportedClass
-         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: ldc           #x                  // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
 
@@ -5510,9 +4674,7 @@
            11      14     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int getValue();
     descriptor: ()I
@@ -5535,19 +4697,15 @@
            11      10     0  this   Lcom/unsupported/UnsupportedClass;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "UnsupportedClass.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
 ## Class: rename_prefix/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.class
   Compiled from "TinyFrameworkToBeRenamed.java"
 public class rename_prefix.com.android.hoststubgen.test.tinyframework.TinyFrameworkToBeRenamed
@@ -5562,16 +4720,14 @@
     flags: (0x0012) ACC_PRIVATE, ACC_FINAL
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   private static {};
     descriptor: ()V
     flags: (0x000a) ACC_PRIVATE, ACC_STATIC
     Code:
       stack=2, locals=0, args_size=0
-         x: ldc           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
          x: return
@@ -5581,7 +4737,7 @@
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=2, args_size=2
-         x: ldc           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
          x: ldc           #x                 // String <init>
          x: ldc           #x                 // String (I)V
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -5599,16 +4755,14 @@
            11      10     1 value   I
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 
   public int getValue();
     descriptor: ()I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=1, args_size=1
-         x: ldc           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed
          x: ldc           #x                 // String getValue
          x: ldc           #x                 // String ()I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
@@ -5622,16 +4776,12 @@
            11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed;
     RuntimeVisibleAnnotations:
       x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-      x: #x()
-        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 }
 SourceFile: "TinyFrameworkToBeRenamed.java"
 RuntimeVisibleAnnotations:
   x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
-  x: #x()
-    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedAsKeep
 RuntimeInvisibleAnnotations:
   x: #x()
-    android.hosttest.annotation.HostSideTestWholeClassStub
+    android.hosttest.annotation.HostSideTestWholeClassKeep
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index f064433..3c138d2 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -1,9 +1,8 @@
-class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy	stub
-  field stub	stub
-  field keep	keep
+class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy	keep
+  field stub	keep
   # field remove	remove # Implicitly remove
-  method <init>	()V	            stub
-  method addOne	(I)I	        stub
+  method <init>	()V	            keep
+  method addOne	(I)I	        keep
   method addOneInner	(I)I	keep
   method toBeRemoved	(Ljava/lang/String;)V	remove
   method addTwo	(I)I	        @addTwo_host
@@ -11,7 +10,7 @@
   method nativeAddThree	(I)I	@addThree_host
   # method addThree_host	(I)I	# used as a substitute
   method unsupportedMethod	()Ljava/lang/String;	throw
-  method visibleButUsesUnsupportedMethod	()Ljava/lang/String;	stub
+  method visibleButUsesUnsupportedMethod	()Ljava/lang/String;	keep
   method toBeIgnoredObj	()Ljava/lang/String;	ignore
   method toBeIgnoredV	()V	ignore
   method toBeIgnoredZ	()Z	ignore
@@ -27,22 +26,22 @@
 class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy	~com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
 
 # Heuristics rule: Stub all the AIDL classes.
-class :aidl stubclass
+class :aidl keepclass
 
 # Heuristics rule: Stub all the R classes.
-class :r stubclass
+class :r keepclass
 
 # Default is "remove", so let's put all the base classes / interfaces in the stub first.
-class com.android.hoststubgen.test.tinyframework.subclasstest.C1 stub
-class com.android.hoststubgen.test.tinyframework.subclasstest.C2 stub
-class com.android.hoststubgen.test.tinyframework.subclasstest.C3 stub
-class com.android.hoststubgen.test.tinyframework.subclasstest.CA stub
-class com.android.hoststubgen.test.tinyframework.subclasstest.CB stub
-class com.android.hoststubgen.test.tinyframework.subclasstest.I1 stub
-class com.android.hoststubgen.test.tinyframework.subclasstest.I2 stub
-class com.android.hoststubgen.test.tinyframework.subclasstest.I3 stub
-class com.android.hoststubgen.test.tinyframework.subclasstest.IA stub
-class com.android.hoststubgen.test.tinyframework.subclasstest.IB stub
+class com.android.hoststubgen.test.tinyframework.subclasstest.C1 keep
+class com.android.hoststubgen.test.tinyframework.subclasstest.C2 keep
+class com.android.hoststubgen.test.tinyframework.subclasstest.C3 keep
+class com.android.hoststubgen.test.tinyframework.subclasstest.CA keep
+class com.android.hoststubgen.test.tinyframework.subclasstest.CB keep
+class com.android.hoststubgen.test.tinyframework.subclasstest.I1 keep
+class com.android.hoststubgen.test.tinyframework.subclasstest.I2 keep
+class com.android.hoststubgen.test.tinyframework.subclasstest.I3 keep
+class com.android.hoststubgen.test.tinyframework.subclasstest.IA keep
+class com.android.hoststubgen.test.tinyframework.subclasstest.IB keep
 
 # Then define inheritance based policies.
 class *com.android.hoststubgen.test.tinyframework.subclasstest.C1 keep
@@ -52,15 +51,15 @@
 class *com.android.hoststubgen.test.tinyframework.subclasstest.IA remove
 
 # Test package directive
-package com.android.hoststubgen.test.tinyframework.packagetest stub
+package com.android.hoststubgen.test.tinyframework.packagetest keep
 class com.android.hoststubgen.test.tinyframework.packagetest.B remove
 class com.android.hoststubgen.test.tinyframework.packagetest.sub.B remove
 # The following rules are the same as above
-# class com.android.hoststubgen.test.tinyframework.packagetest.A stub
-# class com.android.hoststubgen.test.tinyframework.packagetest.sub.A stub
+# class com.android.hoststubgen.test.tinyframework.packagetest.A keep
+# class com.android.hoststubgen.test.tinyframework.packagetest.sub.A keep
 
 # Used to test method call replacement.
-class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace stubclass
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace keepclass
   method originalAdd (II)I @com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo.add
 
 # Used to test method call replacement.
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
index 872bbf8..80ebf3a 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -43,8 +43,7 @@
 
 tiny_framework_classes=$out/tiny-framework/classes/
 tiny_framework_jar=$out/tiny-framework.jar
-tiny_framework_host_stub_jar=$out/tiny-framework_host_stub.jar
-tiny_framework_host_impl_jar=$out/tiny-framework_host_impl.jar
+tiny_framework_host_jar=$out/tiny-framework_host.jar
 
 tiny_test_classes=$out/tiny-test/classes/
 tiny_test_jar=$out/tiny-test.jar
@@ -87,8 +86,7 @@
 run $HOSTSTUBGEN \
     @../hoststubgen-standard-options.txt \
     --in-jar $tiny_framework_jar \
-    --out-stub-jar $tiny_framework_host_stub_jar \
-    --out-impl-jar $tiny_framework_host_impl_jar \
+    --out-jar $tiny_framework_host_jar \
     --policy-override-file policy-override-tiny-framework.txt \
     --gen-keep-all-file out/tiny-framework_keep_all.txt \
     --gen-input-dump-file out/tiny-framework_dump.txt \
@@ -97,14 +95,14 @@
     $HOSTSTUBGEN_OPTS
 
 # Extract the jar files, so we can look into them.
-extract $tiny_framework_host_stub_jar $tiny_framework_host_impl_jar
+extract $tiny_framework_host_jar
 
 # Build the test
 echo "# Building tiny-test..."
 run $JAVAC \
     -cp $( \
         join : \
-        $tiny_framework_host_stub_jar \
+        $tiny_framework_jar \
         "${test_compile_classpaths[@]}" \
         ) \
     -d $tiny_test_classes \
@@ -124,7 +122,7 @@
     -cp $( \
         join : \
         $tiny_test_jar \
-        $tiny_framework_host_impl_jar \
+        $tiny_framework_host_jar \
         "${test_compile_classpaths[@]}" \
         "${test_runtime_classpaths[@]}" \
         ) \
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
similarity index 80%
rename from tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
rename to tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
index 6d8a48a..ed0fa26 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java
@@ -18,39 +18,30 @@
 import android.hosttest.annotation.HostSideTestClassLoadHook;
 import android.hosttest.annotation.HostSideTestKeep;
 import android.hosttest.annotation.HostSideTestRemove;
-import android.hosttest.annotation.HostSideTestStub;
 import android.hosttest.annotation.HostSideTestSubstitute;
 import android.hosttest.annotation.HostSideTestThrow;
 
 /**
  * Test without class-wide annotations.
  */
-@HostSideTestStub
+@HostSideTestKeep
 @HostSideTestClassLoadHook(
         "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
-public class TinyFrameworkClassAnnotations {
-    @HostSideTestStub
-    public TinyFrameworkClassAnnotations() {
+public class TinyFrameworkAnnotations {
+    @HostSideTestKeep
+    public TinyFrameworkAnnotations() {
     }
 
-    @HostSideTestStub
-    public int stub = 1;
-
     @HostSideTestKeep
-    public int keep = 2;
+    public int keep = 1;
 
     // Members will be deleted by default.
     // Deleted fields cannot have an initial value, because otherwise .ctor will fail to set it at
     // runtime.
     public int remove;
 
-    @HostSideTestStub
-    public int addOne(int value) {
-        return addOneInner(value);
-    }
-
     @HostSideTestKeep
-    public int addOneInner(int value) {
+    public int addOne(int value) {
         return value + 1;
     }
 
@@ -59,7 +50,6 @@
         throw new RuntimeException();
     }
 
-    @HostSideTestStub
     @HostSideTestSubstitute(suffix = "_host")
     public int addTwo(int value) {
         throw new RuntimeException("not supported on host side");
@@ -69,7 +59,6 @@
         return value + 2;
     }
 
-    @HostSideTestStub
     @HostSideTestSubstitute(suffix = "_host")
     public static native int nativeAddThree(int value);
 
@@ -82,9 +71,4 @@
     public String unsupportedMethod() {
         return "This value shouldn't be seen on the host side.";
     }
-
-    @HostSideTestStub
-    public String visibleButUsesUnsupportedMethod() {
-        return unsupportedMethod();
-    }
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.java
deleted file mode 100644
index f530207..0000000
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.java
+++ /dev/null
@@ -1,57 +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.hoststubgen.test.tinyframework;
-
-import android.hosttest.annotation.HostSideTestKeep;
-import android.hosttest.annotation.HostSideTestStub;
-import android.hosttest.annotation.HostSideTestWholeClassStub;
-
-/**
- * Used by the benchmark.
- */
-@HostSideTestWholeClassStub
-public class TinyFrameworkCallerCheck {
-
-    /**
-     * This method uses an inner method (which has the caller check).
-     *
-     * Benchmark result: 768ns
-     */
-    public static int getOne_withCheck() {
-        return Impl.getOneKeep();
-    }
-
-    /**
-     * This method doesn't have any caller check.
-     *
-     * Benchmark result: 2ns
-     */
-    public static int getOne_noCheck() {
-        return Impl.getOneStub();
-    }
-
-    private static class Impl {
-        @HostSideTestKeep
-        public static int getOneKeep() {
-            return 1;
-        }
-
-        @HostSideTestStub
-        public static int getOneStub() {
-            return 1;
-        }
-    }
-}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.java
deleted file mode 100644
index 145b65a..0000000
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.java
+++ /dev/null
@@ -1,74 +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.hoststubgen.test.tinyframework;
-
-import android.hosttest.annotation.HostSideTestStub;
-import android.hosttest.annotation.HostSideTestSubstitute;
-import android.hosttest.annotation.HostSideTestWholeClassStub;
-
-@HostSideTestWholeClassStub
-public class TinyFrameworkClassClassWideAnnotations {
-    public TinyFrameworkClassClassWideAnnotations() {
-    }
-
-    public int stub = 1;
-
-    public int keep = 2;
-
-    // Cannot have an initial value, because otherwise .ctor will fail to set it at runtime.
-    public int remove;
-
-    // @Stub
-    public int addOne(int value) {
-        return addOneInner(value);
-    }
-
-    // @Keep
-    public int addOneInner(int value) {
-        return value + 1;
-    }
-
-    // @Remove
-    public void toBeRemoved(String foo) {
-        throw new RuntimeException();
-    }
-
-    @HostSideTestStub
-    @HostSideTestSubstitute(suffix = "_host")
-    public int addTwo(int value) {
-        throw new RuntimeException("not supported on host side");
-    }
-
-    public int addTwo_host(int value) {
-        return value + 2;
-    }
-
-    @HostSideTestStub
-    @HostSideTestSubstitute(suffix = "_host")
-    public static native int nativeAddThree(int value);
-
-    public static int nativeAddThree_host(int value) {
-        return value + 3;
-    }
-
-    public String unsupportedMethod() {
-        return "This value shouldn't be seen on the host side.";
-    }
-
-    public String visibleButUsesUnsupportedMethod() {
-        return unsupportedMethod();
-    }
-}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java
index 98fc634..f734790 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java
@@ -15,12 +15,12 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
 
 import java.util.HashSet;
 import java.util.Set;
 
-@HostSideTestWholeClassStub
+@HostSideTestWholeClassKeep
 public class TinyFrameworkClassLoadHook {
     private TinyFrameworkClassLoadHook() {
     }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java
new file mode 100644
index 0000000..e83163e
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java
@@ -0,0 +1,55 @@
+/*
+ * 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.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestRemove;
+import android.hosttest.annotation.HostSideTestSubstitute;
+import android.hosttest.annotation.HostSideTestThrow;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
+
+@HostSideTestWholeClassKeep
+public class TinyFrameworkClassWideAnnotations {
+    public TinyFrameworkClassWideAnnotations() {
+    }
+
+    public int keep = 1;
+
+    @HostSideTestRemove
+    public int remove;
+
+    public int addOne(int value) {
+        return value + 1;
+    }
+
+    @HostSideTestSubstitute(suffix = "_host")
+    public int addTwo(int value) {
+        throw new RuntimeException("not supported on host side");
+    }
+
+    public int addTwo_host(int value) {
+        return value + 2;
+    }
+
+    @HostSideTestRemove
+    public void toBeRemoved(String foo) {
+        throw new RuntimeException();
+    }
+
+    @HostSideTestThrow
+    public String unsupportedMethod() {
+        return "This value shouldn't be seen on the host side.";
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.java
index 8324ed9..3df21d9 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.java
@@ -15,18 +15,16 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
-import android.hosttest.annotation.HostSideTestClassLoadHook;
-import android.hosttest.annotation.HostSideTestStub;
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestKeep;
 
-@HostSideTestStub
+@HostSideTestKeep
 public class TinyFrameworkClassWithInitializerDefault {
     static {
         sInitialized = true;
     }
 
-    @HostSideTestStub
+    @HostSideTestKeep
     public static boolean sInitialized;
-    @HostSideTestStub
+    @HostSideTestKeep
     public static Object sObject = new Object();
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.java
index ea1ad93..cc665de 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.java
@@ -16,20 +16,20 @@
 package com.android.hoststubgen.test.tinyframework;
 
 import android.hosttest.annotation.HostSideTestClassLoadHook;
+import android.hosttest.annotation.HostSideTestKeep;
 import android.hosttest.annotation.HostSideTestStaticInitializerKeep;
-import android.hosttest.annotation.HostSideTestStub;
 
 @HostSideTestClassLoadHook(
         "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
-@HostSideTestStub
+@HostSideTestKeep
 @HostSideTestStaticInitializerKeep
 public class TinyFrameworkClassWithInitializerStub {
     static {
         sInitialized = true;
     }
 
-    @HostSideTestStub
+    @HostSideTestKeep
     public static boolean sInitialized;
-    @HostSideTestStub
+    @HostSideTestKeep
     public static Object sObject = new Object();
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java
index 51f4818..f833ad8 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java
@@ -16,15 +16,14 @@
 package com.android.hoststubgen.test.tinyframework;
 
 import android.hosttest.annotation.HostSideTestKeep;
-import android.hosttest.annotation.HostSideTestStub;
 
-@HostSideTestStub
+@HostSideTestKeep
 public enum TinyFrameworkEnumComplex {
-    @HostSideTestStub
+    @HostSideTestKeep
     RED("Red", "R"),
-    @HostSideTestStub
+    @HostSideTestKeep
     GREEN("Green", "G"),
-    @HostSideTestStub
+    @HostSideTestKeep
     BLUE("Blue", "B");
 
     @HostSideTestKeep
@@ -33,18 +32,18 @@
     @HostSideTestKeep
     private final String mShortName;
 
-    @HostSideTestStub
+    @HostSideTestKeep
     TinyFrameworkEnumComplex(String longName, String shortName) {
         mLongName = longName;
         mShortName = shortName;
     }
 
-    @HostSideTestStub
+    @HostSideTestKeep
     public String getLongName() {
         return mLongName;
     }
 
-    @HostSideTestStub
+    @HostSideTestKeep
     public String getShortName() {
         return mShortName;
     }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java
index f440d86..c023169 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java
@@ -15,12 +15,12 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
-import android.hosttest.annotation.HostSideTestStub;
+import android.hosttest.annotation.HostSideTestKeep;
 
-@HostSideTestStub
+@HostSideTestKeep
 public enum TinyFrameworkEnumSimple {
-    @HostSideTestStub
+    @HostSideTestKeep
     CAT,
-    @HostSideTestStub
+    @HostSideTestKeep
     DOG,
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java
index 909d3b4..f7cae7d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java
@@ -15,9 +15,9 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
 
-@HostSideTestWholeClassStub
+@HostSideTestWholeClassKeep
 public class TinyFrameworkExceptionTester {
     public static int testException() {
         try {
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java
index 1977c90..ec1efba 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java
@@ -24,17 +24,11 @@
 
     public int stub = 1;
 
-    public int keep = 2;
-
     // Removed fields cannot have an initial value, because otherwise .ctor will fail to set it at
     // runtime.
     public int remove;
 
     public int addOne(int value) {
-        return addOneInner(value);
-    }
-
-    public int addOneInner(int value) {
         return value + 1;
     }
 
@@ -95,8 +89,4 @@
     public String unsupportedMethod() {
         return "This value shouldn't be seen on the host side.";
     }
-
-    public String visibleButUsesUnsupportedMethod() {
-        return unsupportedMethod();
-    }
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.java
index 0d1203b..1ca653e 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.java
@@ -15,8 +15,8 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
+import android.hosttest.annotation.HostSideTestKeep;
 import android.hosttest.annotation.HostSideTestStaticInitializerKeep;
-import android.hosttest.annotation.HostSideTestStub;
 
 import java.util.function.Supplier;
 
@@ -28,48 +28,48 @@
  *
  * Implicit filter should take care of them.
  */
-@HostSideTestStub
+@HostSideTestKeep
 @HostSideTestStaticInitializerKeep
 public class TinyFrameworkLambdas {
-    @HostSideTestStub
+    @HostSideTestKeep
     public TinyFrameworkLambdas() {
     }
 
-    @HostSideTestStub
+    @HostSideTestKeep
     public final Supplier<Integer> mSupplier = () -> 1;
 
-    @HostSideTestStub
+    @HostSideTestKeep
     public static final Supplier<Integer> sSupplier = () -> 2;
 
-    @HostSideTestStub
+    @HostSideTestKeep
     public Supplier<Integer> getSupplier() {
         return () -> 3;
     }
 
-    @HostSideTestStub
+    @HostSideTestKeep
     public static Supplier<Integer> getSupplier_static() {
         return () -> 4;
     }
 
-    @HostSideTestStub
+    @HostSideTestKeep
     @HostSideTestStaticInitializerKeep
     public static class Nested {
-        @HostSideTestStub
+        @HostSideTestKeep
         public Nested() {
         }
 
-        @HostSideTestStub
+        @HostSideTestKeep
         public final Supplier<Integer> mSupplier = () -> 5;
 
-        @HostSideTestStub
+        @HostSideTestKeep
         public static final Supplier<Integer> sSupplier = () -> 6;
 
-        @HostSideTestStub
+        @HostSideTestKeep
         public Supplier<Integer> getSupplier() {
             return () -> 7;
         }
 
-        @HostSideTestStub
+        @HostSideTestKeep
         public static Supplier<Integer> getSupplier_static() {
             return () -> 8;
         }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java
index 1ff3744..57c69a3 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java
@@ -15,11 +15,11 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
-@HostSideTestWholeClassStub
+@HostSideTestWholeClassKeep
 public class TinyFrameworkMethodCallReplace {
     //  This method should return true.
     public static boolean nonStaticMethodCallReplaceTester() throws Exception {
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
index 09ee183..73b5e2f 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
@@ -15,11 +15,12 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
+import android.hosttest.annotation.HostSideTestKeep;
 import android.hosttest.annotation.HostSideTestNativeSubstitutionClass;
 import android.hosttest.annotation.HostSideTestThrow;
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
 
-@HostSideTestWholeClassStub
+@HostSideTestWholeClassKeep
 @HostSideTestNativeSubstitutionClass("TinyFrameworkNative_host")
 public class TinyFrameworkNative {
     public static native int nativeAddTwo(int arg);
@@ -49,6 +50,9 @@
     @HostSideTestThrow
     public static native void nativeStillNotSupported();
 
+    @HostSideTestKeep
+    public static native void nativeStillKeep();
+
     public static void nativeStillNotSupported_should_be_like_this() {
         throw new RuntimeException();
     }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java
index e1c48bb..c1ea2ee 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java
@@ -15,11 +15,11 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
 
 import java.util.function.Supplier;
 
-@HostSideTestWholeClassStub
+@HostSideTestWholeClassKeep
 public class TinyFrameworkNestedClasses {
     public final Supplier<Integer> mSupplier = new Supplier<Integer>() {
         @Override
@@ -34,6 +34,7 @@
             return 2;
         }
     };
+
     public Supplier<Integer> getSupplier() {
         return new Supplier<Integer>() {
             @Override
@@ -52,12 +53,10 @@
         };
     }
 
-    @HostSideTestWholeClassStub
     public class InnerClass {
         public int value = 5;
     }
 
-    @HostSideTestWholeClassStub
     public static class StaticNestedClass {
         public int value = 6;
 
@@ -70,6 +69,10 @@
                 }
             };
         }
+
+        public static class Double$NestedClass {
+            public int value = 8;
+        }
     }
 
     public static class BaseClass {
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.java
index a82be54..941fcff 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.java
@@ -15,9 +15,9 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
 
-@HostSideTestWholeClassStub
+@HostSideTestWholeClassKeep
 public class TinyFrameworkPackageRedirect {
     /**
      * A method that uses "unsupported" class. HostStubGen will redirect them to the "supported"
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java
index 31a164a..707bc0e 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java
@@ -15,9 +15,9 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
 
-@HostSideTestWholeClassStub
+@HostSideTestWholeClassKeep
 public class TinyFrameworkRenamedClassCaller {
     /** Calls the class that'll be renamed. */
     public static int foo(int value) {
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java
index 1430bcb..8319ced 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java
@@ -15,12 +15,12 @@
  */
 package com.android.hoststubgen.test.tinyframework;
 
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
 
 /**
  * This class will be renamed by the "rename" directive in the policy file.
  */
-@HostSideTestWholeClassStub
+@HostSideTestWholeClassKeep
 public class TinyFrameworkToBeRenamed {
     private final int mValue;
 
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/unsupported/UnsupportedClass.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/unsupported/UnsupportedClass.java
index 0409b02..92f41ac 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/unsupported/UnsupportedClass.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/unsupported/UnsupportedClass.java
@@ -15,10 +15,10 @@
  */
 package com.unsupported;
 
-import android.hosttest.annotation.HostSideTestWholeClassStub;
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
 
 // Used for testing --package-redirect.
-@HostSideTestWholeClassStub
+@HostSideTestWholeClassKeep
 public class UnsupportedClass {
     public UnsupportedClass(int value) {
         throw new RuntimeException("This class is not supported");
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java
new file mode 100644
index 0000000..1ae0493
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.hoststubgen.test.tinyframework;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class TinyFrameworkAnnotationsTest {
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    @Test
+    public void testSimple() {
+        TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
+        assertThat(tfc.addOne(1)).isEqualTo(2);
+        assertThat(tfc.keep).isEqualTo(1);
+    }
+
+    @Test
+    public void testRemove() {
+        TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
+        assertThrows(NoSuchMethodError.class, () -> tfc.toBeRemoved("abc"));
+        assertThrows(NoSuchFieldError.class, () -> tfc.remove = 1);
+    }
+
+    @Test
+    public void testSubstitute() {
+        TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
+        assertThat(tfc.addTwo(1)).isEqualTo(3);
+    }
+
+    @Test
+    public void testSubstituteNative() {
+        TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
+        assertThat(tfc.nativeAddThree(1)).isEqualTo(4);
+    }
+
+    @Test
+    public void testUnsupportedMethod() {
+        TinyFrameworkAnnotations tfc = new TinyFrameworkAnnotations();
+
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage("not yet supported");
+        tfc.unsupportedMethod();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkBenchmark.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkBenchmark.java
deleted file mode 100644
index d57735b..0000000
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkBenchmark.java
+++ /dev/null
@@ -1,71 +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.hoststubgen.test.tinyframework;
-
-import org.junit.Test;
-
-import java.text.DecimalFormat;
-
-/**
- * Contains simple micro-benchmarks.
- */
-@LargeTest
-public class TinyFrameworkBenchmark {
-    private static final int MINIMAL_ITERATION = 1000;
-    private static final int MEASURE_SECONDS = 1;
-
-    private static final DecimalFormat sFormatter = new DecimalFormat("#,###");
-
-    private void doBenchmark(String name, Runnable r) {
-        // Worm up
-        for (int i = 0; i < MINIMAL_ITERATION; i++) {
-            r.run();
-        }
-
-        // Start measuring.
-        final long start = System.nanoTime();
-        final long end = start + MEASURE_SECONDS * 1_000_000_000L;
-
-        double iteration = 0;
-        while (System.nanoTime() <= end) {
-            for (int i = 0; i < MINIMAL_ITERATION; i++) {
-                r.run();
-            }
-            iteration += MINIMAL_ITERATION;
-        }
-
-        final long realEnd = System.nanoTime();
-
-        System.out.println(String.format("%s\t%s", name,
-                sFormatter.format((((double) realEnd - start)) / iteration)));
-    }
-
-    /**
-     * Micro-benchmark for a method without a non-stub caller check.
-     */
-    @Test
-    public void benchNoCallerCheck() {
-        doBenchmark("No caller check", TinyFrameworkCallerCheck::getOne_noCheck);
-    }
-
-    /**
-     * Micro-benchmark for a method with a non-stub caller check.
-     */
-    @Test
-    public void benchWithCallerCheck() {
-        doBenchmark("With caller check", TinyFrameworkCallerCheck::getOne_withCheck);
-    }
-}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index 1692c6e89..14229a0 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -17,10 +17,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
 
 import com.android.hoststubgen.test.tinyframework.R.Nested;
-import com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.SubClass;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -41,16 +40,12 @@
         assertThat(tfc.stub).isEqualTo(1);
     }
 
-//    @Test
-//    public void testDoesntCompile() {
-//        TinyFrameworkClass tfc = new TinyFrameworkClass();
-//
-//        tfc.addOneInner(1); // Shouldn't compile.
-//        tfc.toBeRemoved("abc"); // Shouldn't compile.
-//        tfc.unsupportedMethod(); // Shouldn't compile.
-//        int a = tfc.keep; // Shouldn't compile
-//        int b = tfc.remove; // Shouldn't compile
-//    }
+    @Test
+    public void testRemove() {
+        TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
+        assertThrows(NoSuchMethodError.class, () -> tfc.toBeRemoved("abc"));
+        assertThrows(NoSuchFieldError.class, () -> tfc.remove = 1);
+    }
 
     @Test
     public void testIgnore() {
@@ -79,48 +74,12 @@
     }
 
     @Test
-    public void testVisibleButUsesUnsupportedMethod() {
+    public void testUnsupportedMethod() {
         TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
 
         thrown.expect(RuntimeException.class);
         thrown.expectMessage("not yet supported");
-        tfc.visibleButUsesUnsupportedMethod();
-    }
-
-    @Test
-    public void testNestedClass1() {
-        assertThat(new TinyFrameworkNestedClasses().mSupplier.get()).isEqualTo(1);
-    }
-
-    @Test
-    public void testNestedClass2() {
-        assertThat(TinyFrameworkNestedClasses.sSupplier.get()).isEqualTo(2);
-    }
-
-    @Test
-    public void testNestedClass3() {
-        assertThat(new TinyFrameworkNestedClasses().getSupplier().get()).isEqualTo(3);
-    }
-
-    @Test
-    public void testNestedClass4() {
-        assertThat(TinyFrameworkNestedClasses.getSupplier_static().get()).isEqualTo(4);
-    }
-
-    @Test
-    public void testNestedClass5() {
-        assertThat((new TinyFrameworkNestedClasses()).new InnerClass().value).isEqualTo(5);
-    }
-
-    @Test
-    public void testNestedClass6() {
-        assertThat(new TinyFrameworkNestedClasses.StaticNestedClass().value).isEqualTo(6);
-    }
-
-    @Test
-    public void testNestedClass7() {
-        assertThat(TinyFrameworkNestedClasses.StaticNestedClass.getSupplier_static().get())
-                .isEqualTo(7);
+        tfc.unsupportedMethod();
     }
 
     @Test
@@ -186,28 +145,22 @@
     }
 
     @Test
-    public void testSubstituteNativeWithThrow() throws Exception {
-        // We can't use TinyFrameworkNative.nativeStillNotSupported() directly in this class,
-        // because @Throw implies @Keep (not @Stub), and we currently compile this test
-        // against the stub jar (so it won't contain @Throw methods).
-        //
-        // But the method exists at runtime, so we can use reflections to call it.
-        //
-        // In the real Ravenwood environment, we don't use HostStubGen's stub jar at all,
-        // so it's not a problem.
+    public void testSubstituteNativeWithThrow() {
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage("not yet supported");
 
-        final var clazz = TinyFrameworkNative.class;
-        final var method = clazz.getMethod("nativeStillNotSupported");
+        TinyFrameworkNative.nativeStillNotSupported();
+    }
 
-        try {
-            method.invoke(null);
+    @Test
+    public void testSubstituteNativeWithKeep() {
+        // We don't want to complicate the test by setting up JNI,
+        // so to test out whether the native method is preserved, we
+        // check whether calling it will throw UnsatisfiedLinkError,
+        // which would only happen on native methods.
+        thrown.expect(UnsatisfiedLinkError.class);
 
-            fail("java.lang.reflect.InvocationTargetException expected");
-
-        } catch (java.lang.reflect.InvocationTargetException e) {
-            var inner = e.getCause();
-            assertThat(inner.getMessage()).contains("not yet supported");
-        }
+        TinyFrameworkNative.nativeStillKeep();
     }
 
     @Test
@@ -216,12 +169,6 @@
         thrown.expectMessage("Outer exception");
 
         TinyFrameworkExceptionTester.testException();
-
-    }
-
-    @Test
-    public void testMethodCallBeforeSuperCall() {
-        assertThat(new SubClass(3).value).isEqualTo(3);
     }
 
     @Test
@@ -231,7 +178,7 @@
         // Having this line before assertThat() will ensure these class are already loaded.
         var classes = new Class[]{
                 TinyFrameworkClassWithInitializerStub.class,
-                TinyFrameworkClassAnnotations.class,
+                TinyFrameworkAnnotations.class,
                 TinyFrameworkForTextPolicy.class,
         };
 
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.java
new file mode 100644
index 0000000..34c98e9
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.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 com.android.hoststubgen.test.tinyframework;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class TinyFrameworkClassWideAnnotationsTest {
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    @Test
+    public void testSimple() {
+        var tfc = new TinyFrameworkClassWideAnnotations();
+        assertThat(tfc.addOne(1)).isEqualTo(2);
+        assertThat(tfc.keep).isEqualTo(1);
+    }
+
+    @Test
+    public void testRemove() {
+        var tfc = new TinyFrameworkClassWideAnnotations();
+        assertThrows(NoSuchMethodError.class, () -> tfc.toBeRemoved("abc"));
+        assertThrows(NoSuchFieldError.class, () -> tfc.remove = 1);
+    }
+
+    @Test
+    public void testSubstitute() {
+        var tfc = new TinyFrameworkClassWideAnnotations();
+        assertThat(tfc.addTwo(1)).isEqualTo(3);
+    }
+
+    @Test
+    public void testUnsupportedMethod() {
+        var tfc = new TinyFrameworkClassWideAnnotations();
+
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage("not yet supported");
+        tfc.unsupportedMethod();
+    }
+
+    @Test
+    public void testMethodCallBeforeSuperCall() {
+        assertThat(new TinyFrameworkNestedClasses.SubClass(3).value).isEqualTo(3);
+    }
+
+    @Test
+    public void testNestedClass1() {
+        assertThat(new TinyFrameworkNestedClasses().mSupplier.get()).isEqualTo(1);
+    }
+
+    @Test
+    public void testNestedClass2() {
+        assertThat(TinyFrameworkNestedClasses.sSupplier.get()).isEqualTo(2);
+    }
+
+    @Test
+    public void testNestedClass3() {
+        assertThat(new TinyFrameworkNestedClasses().getSupplier().get()).isEqualTo(3);
+    }
+
+    @Test
+    public void testNestedClass4() {
+        assertThat(TinyFrameworkNestedClasses.getSupplier_static().get()).isEqualTo(4);
+    }
+
+    @Test
+    public void testNestedClass5() {
+        assertThat((new TinyFrameworkNestedClasses()).new InnerClass().value).isEqualTo(5);
+    }
+
+    @Test
+    public void testNestedClass6() {
+        assertThat(new TinyFrameworkNestedClasses.StaticNestedClass().value).isEqualTo(6);
+    }
+
+    @Test
+    public void testNestedClass7() {
+        assertThat(TinyFrameworkNestedClasses.StaticNestedClass.getSupplier_static().get())
+                .isEqualTo(7);
+    }
+
+    @Test
+    public void testNestedClass8() {
+        assertThat(new TinyFrameworkNestedClasses.StaticNestedClass.Double$NestedClass().value)
+                .isEqualTo(8);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
deleted file mode 100644
index 288c716..0000000
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
+++ /dev/null
@@ -1,66 +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.hoststubgen.test.tinyframework;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-public class TinyFrameworkClassWithAnnotTest {
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
-
-    @Test
-    public void testSimple() {
-        TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
-        assertThat(tfc.addOne(1)).isEqualTo(2);
-        assertThat(tfc.stub).isEqualTo(1);
-    }
-
-//    @Test
-//    public void testDoesntCompile() {
-//        TinyFrameworkClassWithAnnot tfc = new TinyFrameworkClassWithAnnot();
-//
-//        tfc.addOneInner(1); // Shouldn't compile.
-//        tfc.toBeRemoved("abc"); // Shouldn't compile.
-//        tfc.unsupportedMethod(); // Shouldn't compile.
-//        int a = tfc.keep; // Shouldn't compile
-//        int b = tfc.remove; // Shouldn't compile
-//    }
-
-    @Test
-    public void testSubstitute() {
-        TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
-        assertThat(tfc.addTwo(1)).isEqualTo(3);
-    }
-
-    @Test
-    public void testSubstituteNative() {
-        TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
-        assertThat(tfc.nativeAddThree(1)).isEqualTo(4);
-    }
-
-    @Test
-    public void testVisibleButUsesUnsupportedMethod() {
-        TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
-
-        thrown.expect(RuntimeException.class);
-        thrown.expectMessage("not yet supported");
-        tfc.visibleButUsesUnsupportedMethod();
-    }
-}
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt
index 6b46c84..5b2795c 100644
--- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt
@@ -23,18 +23,6 @@
 import org.objectweb.asm.Opcodes.ACC_STATIC
 
 class AsmUtilsTest {
-    private fun checkGetDirectOuterClassName(input: String, expected: String?) {
-        assertThat(getDirectOuterClassName(input)).isEqualTo(expected)
-    }
-
-    @Test
-    fun testGetDirectOuterClassName() {
-        checkGetDirectOuterClassName("a", null)
-        checkGetDirectOuterClassName("a\$x", "a")
-        checkGetDirectOuterClassName("a.b.c\$x", "a.b.c")
-        checkGetDirectOuterClassName("a.b.c\$x\$y", "a.b.c\$x")
-    }
-
     @Test
     fun testVisibility() {
         fun test(access: Int, expected: Visibility) {
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt
index f651514..85b6e80 100644
--- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt
@@ -72,6 +72,18 @@
     }
 
     @Test
+    fun testNestedClass() {
+        val f = ClassFilter.buildFromString("a.b.c\nm.n.o\$p\n", false, "X")
+        assertThat(f.matches("a/b/c")).isEqualTo(true)
+        assertThat(f.matches("a/b/c\$d")).isEqualTo(true)
+        assertThat(f.matches("a/b/c\$d\$e")).isEqualTo(true)
+        assertThat(f.matches("m/n/o")).isEqualTo(false)
+        assertThat(f.matches("m/n/o\$p")).isEqualTo(true)
+        assertThat(f.matches("m/n/o\$p\$r")).isEqualTo(true)
+        assertThat(f.matches("m/n/o\$p\$r\$")).isEqualTo(true)
+    }
+
+    @Test
     fun testBadFilter1() {
         try {
             ClassFilter.buildFromString("""
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")
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index b0f68f7..f68ae2c 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -98,6 +98,18 @@
         }
 
         @Override
+        public void onServiceDisconnected() {
+            if (mCallback != null) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> mCallback.onServiceDisconnected());
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }
+
+        @Override
         public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) {
             if (mCallback != null) {
                 final long token = Binder.clearCallingIdentity();
@@ -247,13 +259,13 @@
                 mService = null;
                 synchronized (mProxyDataLock) {
                     if (!mCallbackProxyCache.isEmpty()) {
-                        mCallbackProxyCache.keySet().forEach(
-                                SharedConnectivityClientCallback::onServiceDisconnected);
+                        mCallbackProxyCache.values().forEach(
+                                SharedConnectivityCallbackProxy::onServiceDisconnected);
                         mCallbackProxyCache.clear();
                     }
                     if (!mProxyMap.isEmpty()) {
-                        mProxyMap.keySet().forEach(
-                                SharedConnectivityClientCallback::onServiceDisconnected);
+                        mProxyMap.values().forEach(
+                                SharedConnectivityCallbackProxy::onServiceDisconnected);
                         mProxyMap.clear();
                     }
                 }
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
index 521f943..7b892af 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
@@ -32,4 +32,5 @@
     oneway void onKnownNetworkConnectionStatusChanged(in KnownNetworkConnectionStatus status);
     oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state);
     oneway void onServiceConnected();
+    oneway void onServiceDisconnected();
 }